Skip to content

Commit f7e260a

Browse files
committed
qt: Add GUIUtil::ExceptionSafeConnect function
Throwing an exception from a slot invoked by Qt's signal-slot connection mechanism is considered undefined behavior, unless it is handled within the slot. The GUIUtil::ExceptionSafeConnect function should be used for exception handling within slots.
1 parent 64a8755 commit f7e260a

File tree

2 files changed

+63
-0
lines changed

2 files changed

+63
-0
lines changed

src/qt/guiutil.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,4 +902,15 @@ QString MakeHtmlLink(const QString& source, const QString& link)
902902
QLatin1String("<a href=\"") % link % QLatin1String("\">") % link % QLatin1String("</a>"));
903903
}
904904

905+
void PrintSlotException(
906+
const std::exception* exception,
907+
const QObject* sender,
908+
const QObject* receiver)
909+
{
910+
std::string description = sender->metaObject()->className();
911+
description += "->";
912+
description += receiver->metaObject()->className();
913+
PrintExceptionContinue(exception, description.c_str());
914+
}
915+
905916
} // namespace GUIUtil

src/qt/guiutil.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,23 @@
99
#include <fs.h>
1010
#include <net.h>
1111
#include <netaddress.h>
12+
#include <util/check.h>
1213

14+
#include <QApplication>
1315
#include <QEvent>
1416
#include <QHeaderView>
1517
#include <QItemDelegate>
1618
#include <QLabel>
1719
#include <QMessageBox>
20+
#include <QMetaObject>
1821
#include <QObject>
1922
#include <QProgressBar>
2023
#include <QString>
2124
#include <QTableView>
2225

26+
#include <cassert>
2327
#include <chrono>
28+
#include <utility>
2429

2530
class QValidatedLineEdit;
2631
class SendCoinsRecipient;
@@ -332,6 +337,53 @@ namespace GUIUtil
332337
*/
333338
QString MakeHtmlLink(const QString& source, const QString& link);
334339

340+
void PrintSlotException(
341+
const std::exception* exception,
342+
const QObject* sender,
343+
const QObject* receiver);
344+
345+
/**
346+
* A drop-in replacement of QObject::connect function
347+
* (see: https://doc.qt.io/qt-5/qobject.html#connect-3), that
348+
* guaranties that all exceptions are handled within the slot.
349+
*
350+
* NOTE: This function is incompatible with Qt private signals.
351+
*/
352+
template <typename Sender, typename Signal, typename Receiver, typename Slot>
353+
auto ExceptionSafeConnect(
354+
Sender sender, Signal signal, Receiver receiver, Slot method,
355+
Qt::ConnectionType type = Qt::AutoConnection)
356+
{
357+
return QObject::connect(
358+
sender, signal, receiver,
359+
[sender, receiver, method](auto&&... args) {
360+
bool ok{true};
361+
try {
362+
(receiver->*method)(std::forward<decltype(args)>(args)...);
363+
} catch (const NonFatalCheckError& e) {
364+
PrintSlotException(&e, sender, receiver);
365+
ok = QMetaObject::invokeMethod(
366+
qApp, "handleNonFatalException",
367+
blockingGUIThreadConnection(),
368+
Q_ARG(QString, QString::fromStdString(e.what())));
369+
} catch (const std::exception& e) {
370+
PrintSlotException(&e, sender, receiver);
371+
ok = QMetaObject::invokeMethod(
372+
qApp, "handleRunawayException",
373+
blockingGUIThreadConnection(),
374+
Q_ARG(QString, QString::fromStdString(e.what())));
375+
} catch (...) {
376+
PrintSlotException(nullptr, sender, receiver);
377+
ok = QMetaObject::invokeMethod(
378+
qApp, "handleRunawayException",
379+
blockingGUIThreadConnection(),
380+
Q_ARG(QString, "Unknown failure occurred."));
381+
}
382+
assert(ok);
383+
},
384+
type);
385+
}
386+
335387
} // namespace GUIUtil
336388

337389
#endif // BITCOIN_QT_GUIUTIL_H

0 commit comments

Comments
 (0)