File indexing completed on 2022-11-23 12:12:11

0001 /*******************************************************************
0002  * backtracewidget.cpp
0003  * SPDX-FileCopyrightText: 2009, 2010 Dario Andres Rodriguez <andresbajotierra@gmail.com>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  *
0007  ******************************************************************/
0008 
0009 #include "backtracewidget.h"
0010 
0011 #include <QDebug>
0012 #include <QLabel>
0013 #include <QScrollBar>
0014 
0015 #include <KLocalizedString>
0016 #include <KMessageBox>
0017 #include <KSyntaxHighlighting/Definition>
0018 #include <KSyntaxHighlighting/Repository>
0019 #include <KSyntaxHighlighting/SyntaxHighlighter>
0020 #include <KSyntaxHighlighting/Theme>
0021 #include <qdesktopservices.h>
0022 
0023 #include "backtracegenerator.h"
0024 #include "backtraceratingwidget.h"
0025 #include "crashedapplication.h"
0026 #include "debuggermanager.h"
0027 #include "drkonqi.h"
0028 #include "drkonqi_globals.h"
0029 #include "parser/backtraceparser.h"
0030 
0031 static const char extraDetailsLabelMargin[] = " margin: 5px; ";
0032 
0033 BacktraceWidget::BacktraceWidget(BacktraceGenerator *generator, QWidget *parent, bool showToggleBacktrace)
0034     : QWidget(parent)
0035     , m_btGenerator(generator)
0036 {
0037     ui.setupUi(this);
0038 
0039     // Debug package installer
0040     m_debugPackageInstaller = new DebugPackageInstaller(this);
0041     connect(m_debugPackageInstaller, &DebugPackageInstaller::error, this, &BacktraceWidget::debugPackageError);
0042     connect(m_debugPackageInstaller, &DebugPackageInstaller::packagesInstalled, this, &BacktraceWidget::regenerateBacktrace);
0043     connect(m_debugPackageInstaller, &DebugPackageInstaller::canceled, this, &BacktraceWidget::debugPackageCanceled);
0044 
0045     connect(m_btGenerator, &BacktraceGenerator::starting, this, &BacktraceWidget::setAsLoading);
0046     connect(m_btGenerator, &BacktraceGenerator::done, this, &BacktraceWidget::loadData);
0047     connect(m_btGenerator, &BacktraceGenerator::someError, this, &BacktraceWidget::loadData);
0048     connect(m_btGenerator, &BacktraceGenerator::failedToStart, this, &BacktraceWidget::loadData);
0049     connect(m_btGenerator, &BacktraceGenerator::newLine, this, &BacktraceWidget::backtraceNewLine);
0050 
0051     connect(ui.m_extraDetailsLabel, &QLabel::linkActivated, this, &BacktraceWidget::extraDetailsLinkActivated);
0052     ui.m_extraDetailsLabel->setVisible(false);
0053     ui.m_extraDetailsLabel->setStyleSheet(QLatin1String(extraDetailsLabelMargin));
0054 
0055     // Setup the buttons
0056     KGuiItem::assign(ui.m_reloadBacktraceButton,
0057                      KGuiItem2(i18nc("@action:button", "&Reload"),
0058                                QIcon::fromTheme(QStringLiteral("view-refresh")),
0059                                i18nc("@info:tooltip",
0060                                      "Use this button to "
0061                                      "reload the crash information (backtrace). This is useful when you have "
0062                                      "installed the proper debug symbol packages and you want to obtain "
0063                                      "a better backtrace.")));
0064     connect(ui.m_reloadBacktraceButton, &QPushButton::clicked, this, &BacktraceWidget::regenerateBacktrace);
0065 
0066     KGuiItem::assign(ui.m_installDebugButton,
0067                      KGuiItem2(i18nc("@action:button", "&Install Debug Symbols"),
0068                                QIcon::fromTheme(QStringLiteral("system-software-update")),
0069                                i18nc("@info:tooltip",
0070                                      "Use this button to "
0071                                      "install the missing debug symbols packages.")));
0072     ui.m_installDebugButton->setVisible(false);
0073     connect(ui.m_installDebugButton, &QPushButton::clicked, this, &BacktraceWidget::installDebugPackages);
0074     if (DrKonqi::crashedApplication()->hasDeletedFiles()) {
0075         ui.m_installDebugButton->setEnabled(false);
0076         ui.m_installDebugButton->setToolTip(i18nc("@info:tooltip",
0077                                                   "Symbol installation is unavailable because the application "
0078                                                   "was updated or uninstalled after it had been started."));
0079     }
0080 
0081     KGuiItem::assign(ui.m_copyButton,
0082                      KGuiItem2(QString(),
0083                                QIcon::fromTheme(QStringLiteral("edit-copy")),
0084                                i18nc("@info:tooltip",
0085                                      "Use this button to copy the "
0086                                      "crash information (backtrace) to the clipboard.")));
0087     connect(ui.m_copyButton, &QPushButton::clicked, this, &BacktraceWidget::copyClicked);
0088     ui.m_copyButton->setEnabled(false);
0089 
0090     KGuiItem::assign(ui.m_saveButton,
0091                      KGuiItem2(QString(),
0092                                QIcon::fromTheme(QStringLiteral("document-save")),
0093                                i18nc("@info:tooltip",
0094                                      "Use this button to save the "
0095                                      "crash information (backtrace) to a file. This is useful "
0096                                      "if you want to take a look at it or to report the bug "
0097                                      "later.")));
0098     connect(ui.m_saveButton, &QPushButton::clicked, this, &BacktraceWidget::saveClicked);
0099     ui.m_saveButton->setEnabled(false);
0100 
0101     // Create the rating widget
0102     m_backtraceRatingWidget = new BacktraceRatingWidget(ui.m_statusWidget);
0103     ui.m_statusWidget->addCustomStatusWidget(m_backtraceRatingWidget);
0104 
0105     ui.m_statusWidget->setIdle(QString());
0106 
0107     // Do we need the "Show backtrace" toggle action ?
0108     if (!showToggleBacktrace) {
0109         ui.mainLayout->removeWidget(ui.m_toggleBacktraceCheckBox);
0110         ui.m_toggleBacktraceCheckBox->setVisible(false);
0111         toggleBacktrace(true);
0112     } else {
0113         // Generate help widget
0114         ui.m_backtraceHelpLabel->setText(
0115             i18n("<h2>What is a \"backtrace\" ?</h2><p>A backtrace basically describes what was "
0116                  "happening inside the application when it crashed, so the developers may track "
0117                  "down where the mess started. They may look meaningless to you, but they might "
0118                  "actually contain a wealth of useful information.<br />Backtraces are commonly "
0119                  "used during interactive and post-mortem debugging.</p>"));
0120         ui.m_backtraceHelpIcon->setPixmap(QIcon::fromTheme(QStringLiteral("help-hint")).pixmap(48, 48));
0121         connect(ui.m_toggleBacktraceCheckBox, &QCheckBox::toggled, this, &BacktraceWidget::toggleBacktrace);
0122         toggleBacktrace(false);
0123     }
0124 
0125     ui.m_backtraceEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0126 }
0127 
0128 void BacktraceWidget::setAsLoading()
0129 {
0130     // remove the syntax highlighter
0131     delete m_highlighter;
0132     m_highlighter = nullptr;
0133 
0134     // Set the widget as loading and disable all the action buttons
0135     ui.m_backtraceEdit->setText(i18nc("@info:status", "Loading..."));
0136     ui.m_backtraceEdit->setEnabled(false);
0137 
0138     ui.m_statusWidget->setBusy(i18nc("@info:status", "Generating backtrace... (this may take some time)"));
0139     m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless);
0140     m_backtraceRatingWidget->setState(BacktraceGenerator::Loading);
0141 
0142     ui.m_extraDetailsLabel->setVisible(false);
0143     ui.m_extraDetailsLabel->clear();
0144 
0145     ui.m_installDebugButton->setVisible(false);
0146     ui.m_reloadBacktraceButton->setEnabled(false);
0147 
0148     ui.m_copyButton->setEnabled(false);
0149     ui.m_saveButton->setEnabled(false);
0150 }
0151 
0152 // Force backtrace generation
0153 void BacktraceWidget::regenerateBacktrace()
0154 {
0155     setAsLoading();
0156 
0157     if (!DrKonqi::debuggerManager()->debuggerIsRunning()) {
0158         m_btGenerator->start();
0159     } else {
0160         anotherDebuggerRunning();
0161     }
0162 
0163     Q_EMIT stateChanged();
0164 }
0165 
0166 void BacktraceWidget::generateBacktrace()
0167 {
0168     if (m_btGenerator->state() == BacktraceGenerator::NotLoaded) {
0169         // First backtrace generation
0170         regenerateBacktrace();
0171     } else if (m_btGenerator->state() == BacktraceGenerator::Loading) {
0172         // Set in loading state, the widget will catch the backtrace events anyway
0173         setAsLoading();
0174         Q_EMIT stateChanged();
0175     } else {
0176         //*Finished* states
0177         setAsLoading();
0178         Q_EMIT stateChanged();
0179         // Load already generated information
0180         loadData();
0181     }
0182 }
0183 
0184 void BacktraceWidget::anotherDebuggerRunning()
0185 {
0186     // As another debugger is running, we should disable the actions and notify the user
0187     ui.m_backtraceEdit->setEnabled(false);
0188     ui.m_backtraceEdit->setText(i18nc("@info",
0189                                       "Another debugger is currently debugging the "
0190                                       "same application. The crash information could not be fetched."));
0191     m_backtraceRatingWidget->setState(BacktraceGenerator::Failed);
0192     m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless);
0193     ui.m_statusWidget->setIdle(i18nc("@info:status", "The crash information could not be fetched."));
0194     ui.m_extraDetailsLabel->setVisible(true);
0195     ui.m_extraDetailsLabel->setText(xi18nc("@info/rich",
0196                                            "Another debugging process is attached to "
0197                                            "the crashed application. Therefore, the DrKonqi debugger cannot "
0198                                            "fetch the backtrace. Please close the other debugger and "
0199                                            "click <interface>Reload</interface>."));
0200     ui.m_installDebugButton->setVisible(false);
0201     ui.m_reloadBacktraceButton->setEnabled(true);
0202 }
0203 
0204 void BacktraceWidget::loadData()
0205 {
0206     // Load the backtrace data from the generator
0207     m_backtraceRatingWidget->setState(m_btGenerator->state());
0208 
0209     if (m_btGenerator->state() == BacktraceGenerator::Loaded) {
0210         ui.m_backtraceEdit->setEnabled(true);
0211         ui.m_backtraceEdit->setPlainText(m_btGenerator->backtrace());
0212 
0213         // scroll to crash
0214         QTextCursor crashCursor = ui.m_backtraceEdit->document()->find(QStringLiteral("[KCrash Handler]"));
0215         if (crashCursor.isNull()) {
0216             crashCursor = ui.m_backtraceEdit->document()->find(QStringLiteral("KCrash::defaultCrashHandler"));
0217         }
0218         if (!crashCursor.isNull()) {
0219             crashCursor.movePosition(QTextCursor::Up, QTextCursor::MoveAnchor);
0220             ui.m_backtraceEdit->verticalScrollBar()->setValue(ui.m_backtraceEdit->cursorRect(crashCursor).top());
0221         }
0222 
0223         // highlight if possible
0224         if (m_btGenerator->debuggerIsGDB()) {
0225             KSyntaxHighlighting::Repository repository;
0226             m_highlighter = new KSyntaxHighlighting::SyntaxHighlighter(ui.m_backtraceEdit->document());
0227             m_highlighter->setTheme((palette().color(QPalette::Base).lightness() < 128) ? repository.defaultTheme(KSyntaxHighlighting::Repository::DarkTheme)
0228                                                                                         : repository.defaultTheme(KSyntaxHighlighting::Repository::LightTheme));
0229 
0230             const auto def = repository.definitionForName(QStringLiteral("GDB Backtrace"));
0231             m_highlighter->setDefinition(def);
0232         }
0233 
0234         BacktraceParser *btParser = m_btGenerator->parser();
0235         m_backtraceRatingWidget->setUsefulness(btParser->backtraceUsefulness());
0236 
0237         // Generate the text to put in the status widget (backtrace usefulness)
0238         QString usefulnessText;
0239         switch (btParser->backtraceUsefulness()) {
0240         case BacktraceParser::ReallyUseful:
0241             usefulnessText = i18nc("@info", "The generated crash information is useful");
0242             break;
0243         case BacktraceParser::MayBeUseful:
0244             usefulnessText = i18nc("@info", "The generated crash information may be useful");
0245             break;
0246         case BacktraceParser::ProbablyUseless:
0247             usefulnessText = i18nc("@info", "The generated crash information is probably not useful");
0248             break;
0249         case BacktraceParser::Useless:
0250             usefulnessText = i18nc("@info", "The generated crash information is not useful");
0251             break;
0252         default:
0253             // let's hope nobody will ever see this... ;)
0254             usefulnessText = i18nc("@info",
0255                                    "The rating of this crash information is invalid. "
0256                                    "This is a bug in DrKonqi itself.");
0257             break;
0258         }
0259         ui.m_statusWidget->setIdle(usefulnessText);
0260 
0261         if (btParser->backtraceUsefulness() != BacktraceParser::ReallyUseful) {
0262             // Not a perfect bactrace, ask the user to try to improve it
0263             ui.m_extraDetailsLabel->setVisible(true);
0264             if (canInstallDebugPackages()) {
0265                 // The script to install the debug packages is available
0266                 ui.m_extraDetailsLabel->setText(xi18nc("@info/rich",
0267                                                        "You can click the <interface>"
0268                                                        "Install Debug Symbols</interface> button in order to automatically "
0269                                                        "install the missing debugging information packages. If this method "
0270                                                        "does not work: please read <link url='%1'>How to "
0271                                                        "create useful crash reports</link> to learn how to get a useful "
0272                                                        "backtrace; install the needed packages (<link url='%2'>"
0273                                                        "list of files</link>) and click the "
0274                                                        "<interface>Reload</interface> button.",
0275                                                        QLatin1String(TECHBASE_HOWTO_DOC),
0276                                                        QLatin1String("#missingDebugPackages")));
0277                 ui.m_installDebugButton->setVisible(true);
0278                 // Retrieve the libraries with missing debug info
0279                 const QStringList missingLibraries = btParser->librariesWithMissingDebugSymbols();
0280                 m_debugPackageInstaller->setMissingLibraries(missingLibraries);
0281             } else {
0282                 // No automated method to install the missing debug info
0283                 // Tell the user to read the howto and reload
0284                 ui.m_extraDetailsLabel->setText(xi18nc("@info/rich",
0285                                                        "Please read <link url='%1'>How to "
0286                                                        "create useful crash reports</link> to learn how to get a useful "
0287                                                        "backtrace; install the needed packages (<link url='%2'>"
0288                                                        "list of files</link>) and click the "
0289                                                        "<interface>Reload</interface> button.",
0290                                                        QLatin1String(TECHBASE_HOWTO_DOC),
0291                                                        QLatin1String("#missingDebugPackages")));
0292             }
0293         }
0294 
0295         ui.m_copyButton->setEnabled(true);
0296         ui.m_saveButton->setEnabled(true);
0297     } else if (m_btGenerator->state() == BacktraceGenerator::Failed) {
0298         // The backtrace could not be generated because the debugger had an error
0299         m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless);
0300 
0301         ui.m_statusWidget->setIdle(i18nc("@info:status", "The debugger has quit unexpectedly."));
0302 
0303         ui.m_backtraceEdit->setPlainText(i18nc("@info:status", "The crash information could not be generated."));
0304 
0305         ui.m_extraDetailsLabel->setVisible(true);
0306         ui.m_extraDetailsLabel->setText(xi18nc("@info/rich",
0307                                                "You could try to regenerate the "
0308                                                "backtrace by clicking the <interface>Reload"
0309                                                "</interface> button."));
0310     } else if (m_btGenerator->state() == BacktraceGenerator::FailedToStart) {
0311         // The backtrace could not be generated because the debugger could not start (missing)
0312         // Tell the user to install it.
0313         m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless);
0314 
0315         ui.m_statusWidget->setIdle(i18nc("@info:status",
0316                                          "<strong>The debugger application is missing or "
0317                                          "could not be launched.</strong>"));
0318 
0319         ui.m_backtraceEdit->setPlainText(i18nc("@info:status", "The crash information could not be generated."));
0320         ui.m_extraDetailsLabel->setVisible(true);
0321         ui.m_extraDetailsLabel->setText(xi18nc("@info/rich",
0322                                                "<strong>You need to first install the debugger "
0323                                                "application (%1) then click the <interface>Reload"
0324                                                "</interface> button.</strong>",
0325                                                m_btGenerator->debuggerName()));
0326     }
0327 
0328     ui.m_reloadBacktraceButton->setEnabled(true);
0329 
0330     Q_EMIT stateChanged();
0331 }
0332 
0333 void BacktraceWidget::backtraceNewLine(const QString &line)
0334 {
0335     // While loading the backtrace (unparsed) a new line was sent from the debugger, append it
0336     ui.m_backtraceEdit->append(line.trimmed());
0337 }
0338 
0339 void BacktraceWidget::copyClicked()
0340 {
0341     ui.m_backtraceEdit->selectAll();
0342     ui.m_backtraceEdit->copy();
0343 }
0344 
0345 void BacktraceWidget::saveClicked()
0346 {
0347     DrKonqi::saveReport(m_btGenerator->backtrace(), this);
0348 }
0349 
0350 void BacktraceWidget::highlightExtraDetailsLabel(bool highlight)
0351 {
0352     const QString stylesheet = ((!highlight) ? QLatin1String("border-width: 0px;")
0353                                              : QLatin1String("border-width: 2px; "
0354                                                              "border-style: solid; "
0355                                                              "border-color: red;"))
0356         + QLatin1String(extraDetailsLabelMargin);
0357 
0358     ui.m_extraDetailsLabel->setStyleSheet(stylesheet);
0359 }
0360 
0361 void BacktraceWidget::focusImproveBacktraceButton()
0362 {
0363     ui.m_installDebugButton->setFocus();
0364 }
0365 
0366 void BacktraceWidget::installDebugPackages()
0367 {
0368     ui.m_installDebugButton->setVisible(false);
0369     m_debugPackageInstaller->installDebugPackages();
0370 }
0371 
0372 void BacktraceWidget::debugPackageError(const QString &errorMessage)
0373 {
0374     ui.m_installDebugButton->setVisible(true);
0375     KMessageBox::error(this,
0376                        errorMessage,
0377                        i18nc("@title:window",
0378                              "Error during the installation of"
0379                              " debug symbols"));
0380 }
0381 
0382 void BacktraceWidget::debugPackageCanceled()
0383 {
0384     ui.m_installDebugButton->setVisible(true);
0385 }
0386 
0387 bool BacktraceWidget::canInstallDebugPackages() const
0388 {
0389     return m_debugPackageInstaller->canInstallDebugPackages();
0390 }
0391 
0392 void BacktraceWidget::toggleBacktrace(bool show)
0393 {
0394     ui.m_backtraceStack->setCurrentWidget(show ? ui.backtracePage : ui.backtraceHelpPage);
0395 }
0396 
0397 void BacktraceWidget::extraDetailsLinkActivated(QString link)
0398 {
0399     if (link.startsWith(QLatin1String("http"))) {
0400         // Open externally
0401         QDesktopServices::openUrl(QUrl(link));
0402     } else if (link == QLatin1String("#missingDebugPackages")) {
0403         BacktraceParser *btParser = m_btGenerator->parser();
0404 
0405         QStringList missingDbgForFiles = btParser->librariesWithMissingDebugSymbols();
0406         missingDbgForFiles.insert(0, DrKonqi::crashedApplication()->executable().absoluteFilePath());
0407 
0408         // HTML message
0409         QString message = QStringLiteral("<html>") + i18n("The packages containing debug information for the following application and libraries are missing:")
0410             + QStringLiteral("<br /><ul>");
0411 
0412         for (const QString &string : std::as_const(missingDbgForFiles)) {
0413             message += QLatin1String("<li>") + string + QLatin1String("</li>");
0414         }
0415 
0416         message += QStringLiteral("</ul></html>");
0417 
0418         KMessageBox::information(this, message, i18nc("messagebox title", "Missing debug information packages"));
0419     }
0420 }