Skip to content

Commit 03eccee

Browse files
committed
Merge bitcoin-core#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/bitcoin#18897, and is based on Russ' [idea](bitcoin/bitcoin#18897 (review)): > IMO it would be nice to have a followup PR that eliminated the one-line forwarding methods ... Related issues - bitcoin-core#91 - bitcoin/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
2 parents b8e5bbd + b8e5d0d commit 03eccee

12 files changed

+114
-9
lines changed

src/qt/bitcoin.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,12 @@
4545
#include <QApplication>
4646
#include <QDebug>
4747
#include <QFontDatabase>
48+
#include <QLatin1String>
4849
#include <QLibraryInfo>
4950
#include <QLocale>
5051
#include <QMessageBox>
5152
#include <QSettings>
53+
#include <QStringBuilder>
5254
#include <QThread>
5355
#include <QTimer>
5456
#include <QTranslator>
@@ -417,10 +419,23 @@ void BitcoinApplication::shutdownResult()
417419

418420
void BitcoinApplication::handleRunawayException(const QString &message)
419421
{
420-
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);
422+
QMessageBox::critical(
423+
nullptr, tr("Runaway exception"),
424+
tr("A fatal error occurred. %1 can no longer continue safely and will quit.").arg(PACKAGE_NAME) %
425+
QLatin1String("<br><br>") % GUIUtil::MakeHtmlLink(message, PACKAGE_BUGREPORT));
421426
::exit(EXIT_FAILURE);
422427
}
423428

429+
void BitcoinApplication::handleNonFatalException(const QString& message)
430+
{
431+
assert(QThread::currentThread() == thread());
432+
QMessageBox::warning(
433+
nullptr, tr("Internal error"),
434+
tr("An internal error occurred. %1 will attempt to continue safely. This is "
435+
"an unexpected bug which can be reported as described below.").arg(PACKAGE_NAME) %
436+
QLatin1String("<br><br>") % GUIUtil::MakeHtmlLink(message, PACKAGE_BUGREPORT));
437+
}
438+
424439
WId BitcoinApplication::getMainWinId() const
425440
{
426441
if (!window)

src/qt/bitcoin.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ public Q_SLOTS:
9999
/// Handle runaway exceptions. Shows a message box with the problem and quits the program.
100100
void handleRunawayException(const QString &message);
101101

102+
/**
103+
* A helper function that shows a message box
104+
* with details about a non-fatal exception.
105+
*/
106+
void handleNonFatalException(const QString& message);
107+
102108
Q_SIGNALS:
103109
void requestedInitialize();
104110
void requestedShutdown();

src/qt/bitcoingui.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller)
654654
m_open_wallet_action->setEnabled(true);
655655
m_open_wallet_action->setMenu(m_open_wallet_menu);
656656

657-
connect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet);
657+
GUIUtil::ExceptionSafeConnect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet);
658658
connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet);
659659

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

src/qt/guiutil.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include <QGuiApplication>
4343
#include <QJsonObject>
4444
#include <QKeyEvent>
45+
#include <QLatin1String>
4546
#include <QLineEdit>
4647
#include <QList>
4748
#include <QLocale>
@@ -54,6 +55,7 @@
5455
#include <QShortcut>
5556
#include <QSize>
5657
#include <QString>
58+
#include <QStringBuilder>
5759
#include <QTextDocument> // for Qt::mightBeRichText
5860
#include <QThread>
5961
#include <QUrlQuery>
@@ -893,4 +895,22 @@ QImage GetImage(const QLabel* label)
893895
#endif
894896
}
895897

898+
QString MakeHtmlLink(const QString& source, const QString& link)
899+
{
900+
return QString(source).replace(
901+
link,
902+
QLatin1String("<a href=\"") % link % QLatin1String("\">") % link % QLatin1String("</a>"));
903+
}
904+
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+
896916
} // namespace GUIUtil

src/qt/guiutil.h

Lines changed: 57 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;
@@ -327,6 +332,58 @@ namespace GUIUtil
327332
QObject::connect(&source, &QObject::destroyed, object, std::forward<Fn>(function), connection);
328333
}
329334

335+
/**
336+
* Replaces a plain text link with an HTML tagged one.
337+
*/
338+
QString MakeHtmlLink(const QString& source, const QString& link);
339+
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+
330387
} // namespace GUIUtil
331388

332389
#endif // BITCOIN_QT_GUIUTIL_H

src/qt/sendcoinsdialog.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *p
129129
ui->customFee->SetAllowEmpty(false);
130130
ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
131131
minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
132+
133+
GUIUtil::ExceptionSafeConnect(ui->sendButton, &QPushButton::clicked, this, &SendCoinsDialog::sendButtonClicked);
132134
}
133135

134136
void SendCoinsDialog::setClientModel(ClientModel *_clientModel)
@@ -375,7 +377,7 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
375377
return true;
376378
}
377379

378-
void SendCoinsDialog::on_sendButton_clicked()
380+
void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
379381
{
380382
if(!model || !model->getOptionsModel())
381383
return;

src/qt/sendcoinsdialog.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public Q_SLOTS:
8080
void updateCoinControlState(CCoinControl& ctrl);
8181

8282
private Q_SLOTS:
83-
void on_sendButton_clicked();
83+
void sendButtonClicked(bool checked);
8484
void on_buttonChooseFee_clicked();
8585
void on_buttonMinimizeFee_clicked();
8686
void removeEntry(SendCoinsEntry* entry);

src/qt/test/wallettests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDe
7373
if (status == CT_NEW) txid = hash;
7474
}));
7575
ConfirmSend();
76-
bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "on_sendButton_clicked");
76+
bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "sendButtonClicked", Q_ARG(bool, false));
7777
assert(invoked);
7878
return txid;
7979
}

src/qt/transactionview.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
199199
connect(transactionView, &QTableView::doubleClicked, this, &TransactionView::doubleClicked);
200200
connect(transactionView, &QTableView::customContextMenuRequested, this, &TransactionView::contextualMenu);
201201

202-
connect(bumpFeeAction, &QAction::triggered, this, &TransactionView::bumpFee);
202+
GUIUtil::ExceptionSafeConnect(bumpFeeAction, &QAction::triggered, this, &TransactionView::bumpFee);
203203
connect(abandonAction, &QAction::triggered, this, &TransactionView::abandonTx);
204204
connect(copyAddressAction, &QAction::triggered, this, &TransactionView::copyAddress);
205205
connect(copyLabelAction, &QAction::triggered, this, &TransactionView::copyLabel);
@@ -424,7 +424,7 @@ void TransactionView::abandonTx()
424424
model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, false);
425425
}
426426

427-
void TransactionView::bumpFee()
427+
void TransactionView::bumpFee([[maybe_unused]] bool checked)
428428
{
429429
if(!transactionView || !transactionView->selectionModel())
430430
return;

src/qt/transactionview.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ private Q_SLOTS:
9999
void openThirdPartyTxUrl(QString url);
100100
void updateWatchOnlyColumn(bool fHaveWatchOnly);
101101
void abandonTx();
102-
void bumpFee();
102+
void bumpFee(bool checked);
103103

104104
Q_SIGNALS:
105105
void doubleClicked(const QModelIndex&);

0 commit comments

Comments
 (0)