Skip to content

Commit 6900d89

Browse files
laanwjknst
authored andcommitted
Merge dashpay#260: Handle exceptions instead of crash
b8e5d0d qt: Handle exceptions in SendCoinsDialog::sendButtonClicked slot (Hennadii Stepanov) 1ac2bc7 qt: Handle exceptions in TransactionView::bumpFee slot (Hennadii Stepanov) bc00e13 qt: Handle exceptions in WalletModel::pollBalanceChanged slot (Hennadii Stepanov) eb6156b qt: Handle exceptions in BitcoinGUI::addWallet slot (Hennadii Stepanov) f7e260a qt: Add GUIUtil::ExceptionSafeConnect function (Hennadii Stepanov) 64a8755 qt: Add BitcoinApplication::handleNonFatalException function (Hennadii Stepanov) af7e365 qt: Make PACKAGE_BUGREPORT link clickable (Hennadii Stepanov) Pull request description: This PR is an alternative to bitcoin#18897, and is based on Russ' [idea](bitcoin#18897 (review)): > IMO it would be nice to have a followup PR that eliminated the one-line forwarding methods ... Related issues - dashpay#91 - bitcoin#18643 Qt docs: https://doc.qt.io/qt-5.12/exceptionsafety.html#exceptions-in-client-code With this PR the GUI handles the wallet-related exception, and: - display it to a user: ![Screenshot from 2021-04-01 02-55-59](https://user-images.githubusercontent.com/32963518/113226183-33ff8480-9298-11eb-8fe6-2168834ab09a.png) - prints a message to `stderr`: ``` ************************ EXCEPTION: 18NonFatalCheckError wallet/wallet.cpp:2677 (IsCurrentForAntiFeeSniping) Internal bug detected: '!chain.findBlock(block_hash, FoundBlock().time(block_time))' You may report this issue here: https://github.com/bitcoin/bitcoin/issues bitcoin in QPushButton->SendCoinsDialog ``` - writes a message to the `debug.log` - and, if the exception is a non-fatal error, leaves the main window running. ACKs for top commit: laanwj: Code review ACK b8e5d0d ryanofsky: Code review ACK b8e5d0d. This is great! I think more improvements are possible but implementation is very clean and I love how targeted each commit is. Changes since last review: adding more explanatory text, making links clickable, reorganizing. Tree-SHA512: a9f2a2ee8e64b993b0dbc454edcbc39c68c8852abb5dc1feb58f601c0e0e8014dca81c72733aa3fb07b619c6f49b823ed20c7d79cc92088a3abe040ed2149727
1 parent 79d19de commit 6900d89

10 files changed

+112
-6
lines changed

src/qt/bitcoin.cpp

+16-1
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,13 @@
4747

4848
#include <QApplication>
4949
#include <QDebug>
50+
#include <QLatin1String>
5051
#include <QLibraryInfo>
5152
#include <QLocale>
5253
#include <QMessageBox>
5354
#include <QProcess>
5455
#include <QSettings>
56+
#include <QStringBuilder>
5557
#include <QThread>
5658
#include <QTimer>
5759
#include <QTranslator>
@@ -491,10 +493,23 @@ void BitcoinApplication::shutdownResult()
491493

492494
void BitcoinApplication::handleRunawayException(const QString &message)
493495
{
494-
QMessageBox::critical(nullptr, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. %1 can no longer continue safely and will quit.").arg(PACKAGE_NAME) + QString("<br><br>") + message);
496+
QMessageBox::critical(
497+
nullptr, tr("Runaway exception"),
498+
tr("A fatal error occurred. %1 can no longer continue safely and will quit.").arg(PACKAGE_NAME) %
499+
QLatin1String("<br><br>") % GUIUtil::MakeHtmlLink(message, PACKAGE_BUGREPORT));
495500
::exit(EXIT_FAILURE);
496501
}
497502

503+
void BitcoinApplication::handleNonFatalException(const QString& message)
504+
{
505+
assert(QThread::currentThread() == thread());
506+
QMessageBox::warning(
507+
nullptr, tr("Internal error"),
508+
tr("An internal error occurred. %1 will attempt to continue safely. This is "
509+
"an unexpected bug which can be reported as described below.").arg(PACKAGE_NAME) %
510+
QLatin1String("<br><br>") % GUIUtil::MakeHtmlLink(message, PACKAGE_BUGREPORT));
511+
}
512+
498513
WId BitcoinApplication::getMainWinId() const
499514
{
500515
if (!window)

src/qt/bitcoin.h

+6
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ public Q_SLOTS:
9696
/// Handle runaway exceptions. Shows a message box with the problem and quits the program.
9797
void handleRunawayException(const QString &message);
9898

99+
/**
100+
* A helper function that shows a message box
101+
* with details about a non-fatal exception.
102+
*/
103+
void handleNonFatalException(const QString& message);
104+
99105
Q_SIGNALS:
100106
void requestedInitialize();
101107
void requestedRestart(QStringList args);

src/qt/bitcoingui.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,7 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller)
891891
m_open_wallet_action->setEnabled(true);
892892
m_open_wallet_action->setMenu(m_open_wallet_menu);
893893

894-
connect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet);
894+
GUIUtil::ExceptionSafeConnect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet);
895895
connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet);
896896

897897
for (WalletModel* wallet_model : m_wallet_controller->getOpenWallets()) {

src/qt/guiutil.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#include <QGuiApplication>
5353
#include <QJsonObject>
5454
#include <QKeyEvent>
55+
#include <QLatin1String>
5556
#include <QLineEdit>
5657
#include <QList>
5758
#include <QLocale>
@@ -65,6 +66,7 @@
6566
#include <QShortcut>
6667
#include <QSize>
6768
#include <QString>
69+
#include <QStringBuilder>
6870
#include <QTextDocument> // for Qt::mightBeRichText
6971
#include <QThread>
7072
#include <QTimer>
@@ -1874,4 +1876,22 @@ QImage GetImage(const QLabel* label)
18741876
#endif
18751877
}
18761878

1879+
QString MakeHtmlLink(const QString& source, const QString& link)
1880+
{
1881+
return QString(source).replace(
1882+
link,
1883+
QLatin1String("<a href=\"") % link % QLatin1String("\">") % link % QLatin1String("</a>"));
1884+
}
1885+
1886+
void PrintSlotException(
1887+
const std::exception* exception,
1888+
const QObject* sender,
1889+
const QObject* receiver)
1890+
{
1891+
std::string description = sender->metaObject()->className();
1892+
description += "->";
1893+
description += receiver->metaObject()->className();
1894+
PrintExceptionContinue(std::make_exception_ptr(exception), description.c_str());
1895+
}
1896+
18771897
} // namespace GUIUtil

src/qt/guiutil.h

+57
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,23 @@
99
#include <fs.h>
1010
#include <qt/guiconstants.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 <QMessageBox>
19+
#include <QMetaObject>
1720
#include <QObject>
1821
#include <QProgressBar>
1922
#include <QString>
2023
#include <QTableView>
2124
#include <QLabel>
2225

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

2530
class QValidatedLineEdit;
2631
class OptionsModel;
@@ -520,6 +525,58 @@ namespace GUIUtil
520525
QObject::connect(&source, &QObject::destroyed, object, std::forward<Fn>(function), connection);
521526
}
522527

528+
/**
529+
* Replaces a plain text link with an HTML tagged one.
530+
*/
531+
QString MakeHtmlLink(const QString& source, const QString& link);
532+
533+
void PrintSlotException(
534+
const std::exception* exception,
535+
const QObject* sender,
536+
const QObject* receiver);
537+
538+
/**
539+
* A drop-in replacement of QObject::connect function
540+
* (see: https://doc.qt.io/qt-5/qobject.html#connect-3), that
541+
* guaranties that all exceptions are handled within the slot.
542+
*
543+
* NOTE: This function is incompatible with Qt private signals.
544+
*/
545+
template <typename Sender, typename Signal, typename Receiver, typename Slot>
546+
auto ExceptionSafeConnect(
547+
Sender sender, Signal signal, Receiver receiver, Slot method,
548+
Qt::ConnectionType type = Qt::AutoConnection)
549+
{
550+
return QObject::connect(
551+
sender, signal, receiver,
552+
[sender, receiver, method](auto&&... args) {
553+
bool ok{true};
554+
try {
555+
(receiver->*method)(std::forward<decltype(args)>(args)...);
556+
} catch (const NonFatalCheckError& e) {
557+
PrintSlotException(&e, sender, receiver);
558+
ok = QMetaObject::invokeMethod(
559+
qApp, "handleNonFatalException",
560+
blockingGUIThreadConnection(),
561+
Q_ARG(QString, QString::fromStdString(e.what())));
562+
} catch (const std::exception& e) {
563+
PrintSlotException(&e, sender, receiver);
564+
ok = QMetaObject::invokeMethod(
565+
qApp, "handleRunawayException",
566+
blockingGUIThreadConnection(),
567+
Q_ARG(QString, QString::fromStdString(e.what())));
568+
} catch (...) {
569+
PrintSlotException(nullptr, sender, receiver);
570+
ok = QMetaObject::invokeMethod(
571+
qApp, "handleRunawayException",
572+
blockingGUIThreadConnection(),
573+
Q_ARG(QString, "Unknown failure occurred."));
574+
}
575+
assert(ok);
576+
},
577+
type);
578+
}
579+
523580
} // namespace GUIUtil
524581

525582
#endif // BITCOIN_QT_GUIUTIL_H

src/qt/sendcoinsdialog.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ SendCoinsDialog::SendCoinsDialog(bool _fCoinJoin, QWidget* parent) :
153153
}
154154

155155
m_coin_control->UseCoinJoin(_fCoinJoin);
156+
157+
GUIUtil::ExceptionSafeConnect(ui->sendButton, &QPushButton::clicked, this, &SendCoinsDialog::sendButtonClicked);
156158
}
157159

158160
void SendCoinsDialog::setClientModel(ClientModel *_clientModel)
@@ -459,7 +461,7 @@ bool SendCoinsDialog::send(const QList<SendCoinsRecipient>& recipients, QString&
459461
return true;
460462
}
461463

462-
void SendCoinsDialog::on_sendButton_clicked()
464+
void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
463465
{
464466
if(!model || !model->getOptionsModel())
465467
return;

src/qt/sendcoinsdialog.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public Q_SLOTS:
8383
void updateCoinControlState(CCoinControl& ctrl);
8484

8585
private Q_SLOTS:
86-
void on_sendButton_clicked();
86+
void sendButtonClicked(bool checked);
8787
void on_buttonChooseFee_clicked();
8888
void on_buttonMinimizeFee_clicked();
8989
void removeEntry(SendCoinsEntry* entry);

src/qt/test/wallettests.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDe
6868
if (status == CT_NEW) txid = hash;
6969
}));
7070
ConfirmSend();
71-
bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "on_sendButton_clicked");
71+
bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "sendButtonClicked", Q_ARG(bool, false));
7272
assert(invoked);
7373
return txid;
7474
}

src/qt/walletmodel.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <qt/addresstablemodel.h>
1313
#include <qt/clientmodel.h>
1414
#include <qt/guiconstants.h>
15+
#include <qt/guiutil.h>
1516
#include <qt/optionsmodel.h>
1617
#include <qt/paymentserver.h>
1718
#include <qt/recentrequeststablemodel.h>
@@ -68,7 +69,10 @@ WalletModel::~WalletModel()
6869
void WalletModel::startPollBalance()
6970
{
7071
// This timer will be fired repeatedly to update the balance
71-
connect(timer, &QTimer::timeout, this, &WalletModel::pollBalanceChanged);
72+
// Since the QTimer::timeout is a private signal, it cannot be used
73+
// in the GUIUtil::ExceptionSafeConnect directly.
74+
connect(timer, &QTimer::timeout, this, &WalletModel::timerTimeout);
75+
GUIUtil::ExceptionSafeConnect(this, &WalletModel::timerTimeout, this, &WalletModel::pollBalanceChanged);
7276
timer->start(MODEL_UPDATE_DELAY);
7377
}
7478

src/qt/walletmodel.h

+2
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@ class WalletModel : public QObject
233233
// Notify that there are now keys in the keypool
234234
void canGetAddressesChanged();
235235

236+
void timerTimeout();
237+
236238
public Q_SLOTS:
237239
/* Starts a timer to periodically update the balance */
238240
void startPollBalance();

0 commit comments

Comments
 (0)