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