1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Author: Milian Wolff, KDAB (milian.wolff@kdab.com)
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of Qt Creator.
8 **
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ****************************************************************************/
26 
27 #include "valgrindsettings.h"
28 #include "valgrindplugin.h"
29 #include "valgrindconfigwidget.h"
30 
31 #include <coreplugin/icore.h>
32 
33 #include <utils/layoutbuilder.h>
34 #include <utils/qtcassert.h>
35 #include <utils/treemodel.h>
36 #include <utils/utilsicons.h>
37 
38 #include <valgrind/xmlprotocol/error.h>
39 
40 #include <QDebug>
41 #include <QFileDialog>
42 #include <QListView>
43 #include <QPushButton>
44 #include <QSettings>
45 #include <QStandardItemModel>
46 
47 using namespace Utils;
48 
49 namespace Valgrind {
50 namespace Internal {
51 
52 //
53 // SuppressionAspect
54 //
55 
56 // This is somewhat unusual, as it looks the same in Global Settings and Project
57 // settings, but behaves differently (Project only stores a diff) depending on
58 // context.
59 
60 const char globalSuppressionKey[] = "Analyzer.Valgrind.SupressionFiles";
61 const char removedProjectSuppressionKey[] = "Analyzer.Valgrind.RemovedSuppressionFiles";
62 const char addedProjectSuppressionKey[] = "Analyzer.Valgrind.AddedSuppressionFiles";
63 
64 class SuppressionAspectPrivate : public QObject
65 {
66     Q_DECLARE_TR_FUNCTIONS(Valgrind::Internal::ValgrindConfigWidget)
67 
68 public:
SuppressionAspectPrivate(SuppressionAspect * q,bool global)69     SuppressionAspectPrivate(SuppressionAspect *q, bool global) : q(q), isGlobal(global) {}
70 
71     void slotAddSuppression();
72     void slotRemoveSuppression();
73     void slotSuppressionSelectionChanged();
74 
75     SuppressionAspect *q;
76     const bool isGlobal;
77 
78     QPointer<QPushButton> addEntry;
79     QPointer<QPushButton> removeEntry;
80     QPointer<QListView> entryList;
81 
82     QStandardItemModel m_model; // The volatile value of this aspect.
83 
84     QStringList globalSuppressionFiles; // Real value, only used for global settings
85 
86     QStringList removedProjectSuppressionFiles; // Part of real value for project settings
87     QStringList addedProjectSuppressionFiles; // Part of real value for project settings
88 };
89 
addSuppressionFile(const QString & suppression)90 void SuppressionAspect::addSuppressionFile(const QString &suppression)
91 {
92     if (d->isGlobal) {
93         d->globalSuppressionFiles.append(suppression);
94     } else {
95         const QStringList globalSuppressions = ValgrindGlobalSettings::instance()->suppressions.value();
96         if (!d->addedProjectSuppressionFiles.contains(suppression))
97             d->addedProjectSuppressionFiles.append(suppression);
98         d->removedProjectSuppressionFiles.removeAll(suppression);
99     }
100     setVolatileValue(value());
101 }
102 
slotAddSuppression()103 void SuppressionAspectPrivate::slotAddSuppression()
104 {
105     ValgrindGlobalSettings *conf = ValgrindGlobalSettings::instance();
106     QTC_ASSERT(conf, return);
107     const QStringList files =
108             QFileDialog::getOpenFileNames(Core::ICore::dialogParent(),
109                       tr("Valgrind Suppression Files"),
110                       conf->lastSuppressionDirectory.value(),
111                       tr("Valgrind Suppression File (*.supp);;All Files (*)"));
112     //dialog.setHistory(conf->lastSuppressionDialogHistory());
113     if (!files.isEmpty()) {
114         for (const QString &file : files)
115             m_model.appendRow(new QStandardItem(file));
116         conf->lastSuppressionDirectory.setValue(QFileInfo(files.at(0)).absolutePath());
117         //conf->setLastSuppressionDialogHistory(dialog.history());
118         if (!isGlobal)
119             q->apply();
120     }
121 }
122 
slotRemoveSuppression()123 void SuppressionAspectPrivate::slotRemoveSuppression()
124 {
125     // remove from end so no rows get invalidated
126     QList<int> rows;
127 
128     QStringList removed;
129     const QModelIndexList selected = entryList->selectionModel()->selectedIndexes();
130     for (const QModelIndex &index : selected) {
131         rows << index.row();
132         removed << index.data().toString();
133     }
134 
135     Utils::sort(rows, std::greater<int>());
136 
137     for (int row : qAsConst(rows))
138         m_model.removeRow(row);
139 
140     if (!isGlobal)
141         q->apply();
142 }
143 
slotSuppressionSelectionChanged()144 void SuppressionAspectPrivate::slotSuppressionSelectionChanged()
145 {
146     removeEntry->setEnabled(entryList->selectionModel()->hasSelection());
147 }
148 
149 //
150 // SuppressionAspect
151 //
152 
SuppressionAspect(bool global)153 SuppressionAspect::SuppressionAspect(bool global)
154 {
155     d = new SuppressionAspectPrivate(this, global);
156 }
157 
~SuppressionAspect()158 SuppressionAspect::~SuppressionAspect()
159 {
160     delete d;
161 }
162 
value() const163 QStringList SuppressionAspect::value() const
164 {
165     // Note: BaseAspect::d->value is /not/ used.
166     if (d->isGlobal)
167         return d->globalSuppressionFiles;
168 
169     QStringList ret = ValgrindGlobalSettings::instance()->suppressions.value();
170     for (const QString &s : d->removedProjectSuppressionFiles)
171         ret.removeAll(s);
172     ret.append(d->addedProjectSuppressionFiles);
173     return ret;
174 }
175 
setValue(const QStringList & val)176 void SuppressionAspect::setValue(const QStringList &val)
177 {
178     if (d->isGlobal) {
179         d->globalSuppressionFiles = val;
180     } else {
181         const QStringList globals = ValgrindGlobalSettings::instance()->suppressions.value();
182         d->addedProjectSuppressionFiles.clear();
183         for (const QString &s : val) {
184             if (!globals.contains(s))
185                 d->addedProjectSuppressionFiles.append(s);
186         }
187         d->removedProjectSuppressionFiles.clear();
188         for (const QString &s : globals) {
189             if (!val.contains(s))
190                 d->removedProjectSuppressionFiles.append(s);
191         }
192     }
193 }
194 
addToLayout(LayoutBuilder & builder)195 void SuppressionAspect::addToLayout(LayoutBuilder &builder)
196 {
197     QTC_CHECK(!d->addEntry);
198     QTC_CHECK(!d->removeEntry);
199     QTC_CHECK(!d->entryList);
200 
201     using namespace Layouting;
202 
203     d->addEntry = new QPushButton(tr("Add..."));
204     d->removeEntry = new QPushButton(tr("Remove"));
205 
206     d->entryList = new QListView;
207     d->entryList->setModel(&d->m_model);
208     d->entryList->setSelectionMode(QAbstractItemView::MultiSelection);
209 
210     connect(d->addEntry, &QPushButton::clicked,
211             d, &SuppressionAspectPrivate::slotAddSuppression);
212     connect(d->removeEntry, &QPushButton::clicked,
213             d, &SuppressionAspectPrivate::slotRemoveSuppression);
214     connect(d->entryList->selectionModel(), &QItemSelectionModel::selectionChanged,
215             d, &SuppressionAspectPrivate::slotSuppressionSelectionChanged);
216 
217     builder.addItem(tr("Suppression files:"));
218     Row group {
219         d->entryList.data(),
220                 Column { d->addEntry.data(), d->removeEntry.data(), Stretch() }
221     };
222     builder.addItem(Span { 2, group });
223 }
224 
fromMap(const QVariantMap & map)225 void SuppressionAspect::fromMap(const QVariantMap &map)
226 {
227     if (d->isGlobal) {
228         d->globalSuppressionFiles = map.value(globalSuppressionKey).toStringList();
229     } else {
230         d->addedProjectSuppressionFiles = map.value(addedProjectSuppressionKey).toStringList();
231         d->removedProjectSuppressionFiles = map.value(removedProjectSuppressionKey).toStringList();
232     }
233     setVolatileValue(value());
234 }
235 
toMap(QVariantMap & map) const236 void SuppressionAspect::toMap(QVariantMap &map) const
237 {
238     auto save = [&map](const QStringList &data, const QString &key) {
239         if (data.isEmpty())
240             map.remove(key);
241         else
242             map.insert(key, data);
243     };
244 
245     if (d->isGlobal) {
246         save(d->globalSuppressionFiles, globalSuppressionKey);
247     } else {
248         save(d->addedProjectSuppressionFiles, addedProjectSuppressionKey);
249         save(d->removedProjectSuppressionFiles, removedProjectSuppressionKey);
250     }
251 }
252 
volatileValue() const253 QVariant SuppressionAspect::volatileValue() const
254 {
255     QStringList ret;
256 
257     for (int i = 0; i < d->m_model.rowCount(); ++i)
258         ret << d->m_model.item(i)->text();
259 
260     return ret;
261 }
262 
setVolatileValue(const QVariant & val)263 void SuppressionAspect::setVolatileValue(const QVariant &val)
264 {
265     const QStringList files = val.toStringList();
266     d->m_model.clear();
267     for (const QString &file : files)
268         d->m_model.appendRow(new QStandardItem(file));
269 }
270 
cancel()271 void SuppressionAspect::cancel()
272 {
273     setVolatileValue(value());
274 }
275 
apply()276 void SuppressionAspect::apply()
277 {
278     setValue(volatileValue().toStringList());
279 }
280 
finish()281 void SuppressionAspect::finish()
282 {
283     setVolatileValue(value()); // Clean up m_model content
284 }
285 
286 //////////////////////////////////////////////////////////////////
287 //
288 // ValgrindBaseSettings
289 //
290 //////////////////////////////////////////////////////////////////
291 
ValgrindBaseSettings(bool global)292 ValgrindBaseSettings::ValgrindBaseSettings(bool global)
293     : suppressions(global)
294 {
295     // Note that this is used twice, once for project settings in the .user files
296     // and once for global settings in QtCreator.ini. This uses intentionally
297     // the same key to facilitate copying using fromMap/toMap.
298     QString base = "Analyzer.Valgrind.";
299 
300     registerAspect(&suppressions);
301 
302     registerAspect(&valgrindExecutable);
303     valgrindExecutable.setSettingsKey(base + "ValgrindExecutable");
304     valgrindExecutable.setDefaultValue("valgrind");
305     valgrindExecutable.setDisplayStyle(StringAspect::PathChooserDisplay);
306     valgrindExecutable.setExpectedKind(PathChooser::Command);
307     valgrindExecutable.setHistoryCompleter("Valgrind.Command.History");
308     valgrindExecutable.setDisplayName(tr("Valgrind Command"));
309     valgrindExecutable.setLabelText(tr("Valgrind executable:"));
310     if (Utils::HostOsInfo::isWindowsHost()) {
311         // On Window we know that we don't have a local valgrind
312         // executable, so having the "Browse" button in the path chooser
313         // (which is needed for the remote executable) is confusing.
314         // FIXME: not deadly, still...
315         //valgrindExecutable. ... buttonAtIndex(0)->hide();
316     }
317 
318     registerAspect(&valgrindArguments);
319     valgrindArguments.setSettingsKey(base + "ValgrindArguments");
320     valgrindArguments.setDisplayStyle(StringAspect::LineEditDisplay);
321     valgrindArguments.setLabelText(tr("Valgrind arguments:"));
322 
323     registerAspect(&selfModifyingCodeDetection);
324     selfModifyingCodeDetection.setSettingsKey(base + "SelfModifyingCodeDetection");
325     selfModifyingCodeDetection.setDefaultValue(DetectSmcStackOnly);
326     selfModifyingCodeDetection.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox);
327     selfModifyingCodeDetection.addOption("No");
328     selfModifyingCodeDetection.addOption("Only on Stack");
329     selfModifyingCodeDetection.addOption("Everywhere");
330     selfModifyingCodeDetection.addOption("Everywhere Except in File-backend Mappings");
331     selfModifyingCodeDetection.setLabelText(tr("Detect self-modifying code:"));
332 
333     // Memcheck
334     registerAspect(&memcheckArguments);
335     memcheckArguments.setSettingsKey(base + "Memcheck.Arguments");
336     memcheckArguments.setDisplayStyle(StringAspect::LineEditDisplay);
337     memcheckArguments.setLabelText(tr("Extra MemCheck arguments:"));
338 
339     registerAspect(&filterExternalIssues);
340     filterExternalIssues.setSettingsKey(base + "FilterExternalIssues");
341     filterExternalIssues.setDefaultValue(true);
342     filterExternalIssues.setIcon(Icons::FILTER.icon());
343     filterExternalIssues.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel);
344     filterExternalIssues.setLabelText(tr("Show Project Costs Only"));
345     filterExternalIssues.setToolTip(tr("Show only profiling info that originated from this project source."));
346 
347     registerAspect(&trackOrigins);
348     trackOrigins.setSettingsKey(base + "TrackOrigins");
349     trackOrigins.setDefaultValue(true);
350     trackOrigins.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel);
351     trackOrigins.setLabelText(tr("Track origins of uninitialized memory"));
352 
353     registerAspect(&showReachable);
354     showReachable.setSettingsKey(base + "ShowReachable");
355     showReachable.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel);
356     showReachable.setLabelText(tr("Show reachable and indirectly lost blocks"));
357 
358     registerAspect(&leakCheckOnFinish);
359     leakCheckOnFinish.setSettingsKey(base + "LeakCheckOnFinish");
360     leakCheckOnFinish.setDefaultValue(LeakCheckOnFinishSummaryOnly);
361     leakCheckOnFinish.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox);
362     leakCheckOnFinish.addOption(tr("No"));
363     leakCheckOnFinish.addOption(tr("Summary Only"));
364     leakCheckOnFinish.addOption(tr("Full"));
365     leakCheckOnFinish.setLabelText(tr("Check for leaks on finish:"));
366 
367     registerAspect(&numCallers);
368     numCallers.setSettingsKey(base + "NumCallers");
369     numCallers.setDefaultValue(25);
370     numCallers.setLabelText(tr("Backtrace frame count:"));
371 
372     // Callgrind
373 
374     registerAspect(&kcachegrindExecutable);
375     kcachegrindExecutable.setSettingsKey(base + "KCachegrindExecutable");
376     kcachegrindExecutable.setDefaultValue("kcachegrind");
377     kcachegrindExecutable.setDisplayStyle(StringAspect::PathChooserDisplay);
378     kcachegrindExecutable.setLabelText(tr("KCachegrind executable:"));
379     kcachegrindExecutable.setExpectedKind(Utils::PathChooser::Command);
380     kcachegrindExecutable.setDisplayName(tr("KCachegrind Command"));
381 
382     registerAspect(&callgrindArguments);
383     callgrindArguments.setSettingsKey(base + "Callgrind.Arguments");
384     callgrindArguments.setDisplayStyle(StringAspect::LineEditDisplay);
385     callgrindArguments.setLabelText(tr("Extra CallGrind arguments:"));
386 
387     registerAspect(&enableEventToolTips);
388     enableEventToolTips.setDefaultValue(true);
389     enableEventToolTips.setSettingsKey(base + "Callgrind.EnableEventToolTips");
390     enableEventToolTips.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel);
391     enableEventToolTips.setLabelText(tr("Show additional information for events in tooltips"));
392 
393     registerAspect(&enableCacheSim);
394     enableCacheSim.setSettingsKey(base + "Callgrind.EnableCacheSim");
395     enableCacheSim.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel);
396     enableCacheSim.setLabelText(tr("Enable cache simulation"));
397     enableCacheSim.setToolTip("<html><head/><body>" + tr(
398         "<p>Does full cache simulation.</p>\n"
399         "<p>By default, only instruction read accesses will be counted (\"Ir\").</p>\n"
400         "<p>\n"
401         "With cache simulation, further event counters are enabled:\n"
402         "<ul><li>Cache misses on instruction reads (\"I1mr\"/\"I2mr\").</li>\n"
403         "<li>Data read accesses (\"Dr\") and related cache misses (\"D1mr\"/\"D2mr\").</li>\n"
404         "<li>Data write accesses (\"Dw\") and related cache misses (\"D1mw\"/\"D2mw\").</li></ul>\n"
405         "</p>") + "</body></html>");
406 
407     registerAspect(&enableBranchSim);
408     enableBranchSim.setSettingsKey(base + "Callgrind.EnableBranchSim");
409     enableBranchSim.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel);
410     enableBranchSim.setLabelText(tr("Enable branch prediction simulation"));
411     enableBranchSim.setToolTip("<html><head/><body>\n" + tr(
412         "<p>Does branch prediction simulation.</p>\n"
413         "<p>Further event counters are enabled: </p>\n"
414         "<ul><li>Number of executed conditional branches and related predictor misses (\n"
415         "\"Bc\"/\"Bcm\").</li>\n"
416         "<li>Executed indirect jumps and related misses of the jump address predictor (\n"
417         "\"Bi\"/\"Bim\").)</li></ul>") + "</body></html>");
418 
419     registerAspect(&collectSystime);
420     collectSystime.setSettingsKey(base + "Callgrind.CollectSystime");
421     collectSystime.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel);
422     collectSystime.setLabelText(tr("Collect system call time"));
423     collectSystime.setToolTip(tr("Collects information for system call times."));
424 
425     registerAspect(&collectBusEvents);
426     collectBusEvents.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel);
427     collectBusEvents.setSettingsKey(base + "Callgrind.CollectBusEvents");
428     collectBusEvents.setLabelText(tr("Collect global bus events"));
429     collectBusEvents.setToolTip(tr("Collect the number of global bus events that are executed. "
430         "The event type \"Ge\" is used for these events."));
431 
432     registerAspect(&minimumInclusiveCostRatio);
433     minimumInclusiveCostRatio.setSettingsKey(base + "Callgrind.MinimumCostRatio");
434     minimumInclusiveCostRatio.setDefaultValue(0.01);
435     minimumInclusiveCostRatio.setSuffix(tr("%"));
436     minimumInclusiveCostRatio.setLabelText(tr("Result view: Minimum event cost:"));
437     minimumInclusiveCostRatio.setToolTip(tr("Limits the amount of results the profiler gives you. "
438          "A lower limit will likely increase performance."));
439 
440     registerAspect(&visualizationMinimumInclusiveCostRatio);
441     visualizationMinimumInclusiveCostRatio.setSettingsKey(base + "Callgrind.VisualisationMinimumCostRatio");
442     visualizationMinimumInclusiveCostRatio.setDefaultValue(10.0);
443     visualizationMinimumInclusiveCostRatio.setLabelText(tr("Visualization: Minimum event cost:"));
444     visualizationMinimumInclusiveCostRatio.setSuffix(tr("%"));
445 
446     registerAspect(&visibleErrorKinds);
447     visibleErrorKinds.setSettingsKey(base + "VisibleErrorKinds");
448     QList<int> defaultErrorKinds;
449     for (int i = 0; i < Valgrind::XmlProtocol::MemcheckErrorKindCount; ++i)
450         defaultErrorKinds << i;
451     visibleErrorKinds.setDefaultValue(defaultErrorKinds);
452 }
453 
454 
455 //////////////////////////////////////////////////////////////////
456 //
457 // ValgrindGlobalSettings
458 //
459 //////////////////////////////////////////////////////////////////
460 
461 static ValgrindGlobalSettings *theGlobalSettings = nullptr;
462 
ValgrindGlobalSettings()463 ValgrindGlobalSettings::ValgrindGlobalSettings()
464     : ValgrindBaseSettings(true)
465 {
466     theGlobalSettings = this;
467 
468     const QString base = "Analyzer.Valgrind";
469 
470     registerAspect(&lastSuppressionDirectory);
471     lastSuppressionDirectory.setSettingsKey(base + "LastSuppressionDirectory");
472 
473     registerAspect(&lastSuppressionHistory);
474     lastSuppressionHistory.setSettingsKey(base + "LastSuppressionHistory");
475 
476     registerAspect(&detectCycles);
477     detectCycles.setSettingsKey(base + "Callgrind.CycleDetection");
478     detectCycles.setDefaultValue(true);
479     detectCycles.setLabelText("O"); // FIXME: Create a real icon
480     detectCycles.setToolTip(tr("Enable cycle detection to properly handle recursive "
481         "or circular function calls."));
482 
483     registerAspect(&costFormat);
484     costFormat.setSettingsKey(base + "Callgrind.CostFormat");
485     costFormat.setDefaultValue(CostDelegate::FormatRelative);
486     costFormat.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox);
487 
488     registerAspect(&shortenTemplates);
489     shortenTemplates.setSettingsKey(base + "Callgrind.ShortenTemplates");
490     shortenTemplates.setDefaultValue(true);
491     shortenTemplates.setLabelText("<>"); // FIXME: Create a real icon
492     shortenTemplates.setToolTip(tr("Remove template parameter lists when displaying function names."));
493 
494     setConfigWidgetCreator([this] { return ValgrindOptionsPage::createSettingsWidget(this); });
495     readSettings();
496 
497     setAutoApply(false);
498 }
499 
instance()500 ValgrindGlobalSettings *ValgrindGlobalSettings::instance()
501 {
502     return theGlobalSettings;
503 }
504 
505 //
506 // Memcheck
507 //
508 
defaultSettings() const509 QVariantMap ValgrindBaseSettings::defaultSettings() const
510 {
511     QVariantMap defaults;
512     forEachAspect([&defaults](BaseAspect *aspect) {
513         defaults.insert(aspect->settingsKey(), aspect->defaultValue());
514     });
515     return defaults;
516 }
517 
518 static const char groupC[] = "Analyzer";
519 
readSettings()520 void ValgrindGlobalSettings::readSettings()
521 {
522     // Read stored values
523     QSettings *settings = Core::ICore::settings();
524     settings->beginGroup(groupC);
525     QVariantMap map;
526     const QStringList childKey = settings->childKeys();
527     for (const QString &key : childKey)
528         map.insert(key, settings->value(key));
529     settings->endGroup();
530 
531     fromMap(map);
532 }
533 
writeSettings() const534 void ValgrindGlobalSettings::writeSettings() const
535 {
536     const QVariantMap defaults = defaultSettings();
537 
538     Utils::QtcSettings *settings = Core::ICore::settings();
539     settings->beginGroup(groupC);
540     QVariantMap map;
541     toMap(map);
542     for (QVariantMap::ConstIterator it = map.constBegin(); it != map.constEnd(); ++it)
543         settings->setValueWithDefault(it.key(), it.value(), defaults.value(it.key()));
544     settings->endGroup();
545 }
546 
547 //////////////////////////////////////////////////////////////////
548 //
549 // ValgrindProjectSettings
550 //
551 //////////////////////////////////////////////////////////////////
552 
ValgrindProjectSettings()553 ValgrindProjectSettings::ValgrindProjectSettings()
554     : ValgrindBaseSettings(false)
555 {
556     setConfigWidgetCreator([this] { return ValgrindOptionsPage::createSettingsWidget(this); });
557 
558     connect(this, &AspectContainer::fromMapFinished, [this] {
559         // FIXME: Update project page e.g. on "Restore Global", aspects
560         // there are 'autoapply', and Aspect::cancel() is normally part of
561         // the 'manual apply' machinery.
562         setAutoApply(false);
563         cancel();
564         setAutoApply(true);
565     });
566 }
567 
568 } // namespace Internal
569 } // namespace Valgrind
570