File indexing completed on 2024-04-28 13:20:01
0001 /******************************************************************* 0002 * reportinterface.cpp 0003 * SPDX-FileCopyrightText: 2009, 2010, 2011 Dario Andres Rodriguez <andresbajotierra@gmail.com> 0004 * SPDX-FileCopyrightText: 2009 George Kiagiadakis <gkiagia@users.sourceforge.net> 0005 * SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org> 0006 * 0007 * SPDX-License-Identifier: GPL-2.0-or-later 0008 * 0009 ******************************************************************/ 0010 0011 #include "reportinterface.h" 0012 0013 #include <chrono> 0014 0015 #include <KIO/TransferJob> 0016 #include <KLocalizedString> 0017 #include <KUserFeedback/Provider> 0018 0019 #include "backtracegenerator.h" 0020 #include "bugzillalib.h" 0021 #include "config-drkonqi.h" 0022 #include "crashedapplication.h" 0023 #include "debuggermanager.h" 0024 #include "drkonqi.h" 0025 #include "drkonqi_debug.h" 0026 #include "parser/backtraceparser.h" 0027 #include "productmapping.h" 0028 #include "systeminformation.h" 0029 0030 // Max size a report may have. This is enforced in bugzilla, hardcoded, and 0031 // cannot be queried through the API, so handle this client-side in a hardcoded 0032 // fashion as well. 0033 static const int s_maxReportSize = 65535; 0034 0035 ReportInterface::ReportInterface(QObject *parent) 0036 : QObject(parent) 0037 , m_duplicate(0) 0038 { 0039 m_bugzillaManager = new BugzillaManager(KDE_BUGZILLA_URL, this); 0040 0041 m_productMapping = new ProductMapping(DrKonqi::crashedApplication(), m_bugzillaManager, this); 0042 0043 // Information the user can provide about the crash 0044 m_userRememberCrashSituation = false; 0045 m_reproducible = ReproducibleUnsure; 0046 m_provideActionsApplicationDesktop = false; 0047 m_provideUnusualBehavior = false; 0048 m_provideApplicationConfigurationDetails = false; 0049 0050 // Do not attach the bug report to any other existent report (create a new one) 0051 m_attachToBugNumber = 0; 0052 0053 connect(&m_sentryBeacon, &SentryBeacon::eventSent, this, [this] { 0054 m_sentryEventSent = true; 0055 maybeDone(); 0056 }); 0057 connect(&m_sentryBeacon, &SentryBeacon::userFeedbackSent, this, [this] { 0058 m_sentryUserFeedbackSent = true; 0059 maybeDone(); 0060 }); 0061 if (KUserFeedback::Provider provider; provider.isEnabled() && provider.telemetryMode() == KUserFeedback::Provider::DetailedUsageStatistics 0062 && !DrKonqi::isTestingBugzilla() && qgetenv("DRKONQI_KDE_BUGZILLA_URL").isEmpty() && !DrKonqi::crashedApplication()->hasDeletedFiles()) { 0063 metaObject()->invokeMethod(this, [this] { 0064 // Send crash event ASAP, if applicable. Trace quality doesn't matter for it. 0065 sendCrashEvent(); 0066 }); 0067 } 0068 } 0069 0070 void ReportInterface::setBugAwarenessPageData(bool rememberSituation, Reproducible reproducible, bool actions, bool unusual, bool configuration) 0071 { 0072 // Save the information the user can provide about the crash from the assistant page 0073 m_userRememberCrashSituation = rememberSituation; 0074 m_reproducible = reproducible; 0075 m_provideActionsApplicationDesktop = actions; 0076 m_provideUnusualBehavior = unusual; 0077 m_provideApplicationConfigurationDetails = configuration; 0078 } 0079 0080 bool ReportInterface::isBugAwarenessPageDataUseful() const 0081 { 0082 // Determine if the assistant should proceed, considering the amount of information 0083 // the user can provide 0084 int rating = selectedOptionsRating(); 0085 0086 // Minimum information required even for a good backtrace. 0087 bool useful = m_userRememberCrashSituation && (rating >= 2 || (m_reproducible == ReproducibleSometimes || m_reproducible == ReproducibleEverytime)); 0088 return useful; 0089 } 0090 0091 int ReportInterface::selectedOptionsRating() const 0092 { 0093 // Check how many information the user can provide and generate a rating 0094 int rating = 0; 0095 if (m_provideActionsApplicationDesktop) { 0096 rating += 3; 0097 } 0098 if (m_provideApplicationConfigurationDetails) { 0099 rating += 2; 0100 } 0101 if (m_provideUnusualBehavior) { 0102 rating += 1; 0103 } 0104 return rating; 0105 } 0106 0107 QString ReportInterface::backtrace() const 0108 { 0109 return m_backtrace; 0110 } 0111 0112 void ReportInterface::setBacktrace(const QString &backtrace) 0113 { 0114 m_backtrace = backtrace; 0115 Q_EMIT backtraceChanged(); 0116 } 0117 0118 QStringList ReportInterface::firstBacktraceFunctions() const 0119 { 0120 return m_firstBacktraceFunctions; 0121 } 0122 0123 void ReportInterface::setFirstBacktraceFunctions(const QStringList &functions) 0124 { 0125 m_firstBacktraceFunctions = functions; 0126 } 0127 0128 QString ReportInterface::title() const 0129 { 0130 return m_reportTitle; 0131 } 0132 0133 void ReportInterface::setTitle(const QString &text) 0134 { 0135 m_reportTitle = text; 0136 Q_EMIT titleChanged(); 0137 } 0138 0139 void ReportInterface::setDetailText(const QString &text) 0140 { 0141 m_reportDetailText = text; 0142 Q_EMIT detailTextChanged(); 0143 } 0144 0145 void ReportInterface::setPossibleDuplicates(const QStringList &list) 0146 { 0147 m_possibleDuplicates = list; 0148 } 0149 0150 QString ReportInterface::generateReportFullText(DrKonqiStamp stamp, Backtrace inlineBacktrace) const 0151 { 0152 // Note: no translations should be done in this function's strings 0153 0154 const CrashedApplication *crashedApp = DrKonqi::crashedApplication(); 0155 const SystemInformation *sysInfo = DrKonqi::systemInformation(); 0156 0157 QString report; 0158 0159 // Program name and versions 0160 report.append(QStringLiteral("Application: %1 (%2)\n").arg(crashedApp->fakeExecutableBaseName(), crashedApp->version())); 0161 if (sysInfo->compiledSources()) { 0162 report.append(QStringLiteral(" (Compiled from sources)\n")); 0163 } else { 0164 report.append(QLatin1Char('\n')); 0165 } 0166 report.append(QStringLiteral("Qt Version: %1\n").arg(sysInfo->qtVersion())); 0167 report.append(QStringLiteral("Frameworks Version: %1\n").arg(sysInfo->frameworksVersion())); 0168 0169 report.append(QStringLiteral("Operating System: %1\n").arg(sysInfo->operatingSystem())); 0170 report.append(QStringLiteral("Windowing System: %1\n").arg(sysInfo->windowSystem())); 0171 0172 // LSB output or manually selected distro 0173 if (!sysInfo->distributionPrettyName().isEmpty()) { 0174 report.append(QStringLiteral("Distribution: %1\n").arg(sysInfo->distributionPrettyName())); 0175 } else if (!sysInfo->bugzillaPlatform().isEmpty() && sysInfo->bugzillaPlatform() != QLatin1String("unspecified")) { 0176 report.append(QStringLiteral("Distribution (Platform): %1\n").arg(sysInfo->bugzillaPlatform())); 0177 } 0178 0179 report.append(QStringLiteral("DrKonqi: %1 [%2]\n").arg(QString::fromLatin1(PROJECT_VERSION), DrKonqi::backendClassName())); 0180 report.append(QLatin1Char('\n')); 0181 0182 // Details of the crash situation 0183 if (isBugAwarenessPageDataUseful()) { 0184 report.append(QStringLiteral("-- Information about the crash:\n")); 0185 if (!m_reportDetailText.isEmpty()) { 0186 report.append(m_reportDetailText.trimmed()); 0187 } else { 0188 // If the user manual reports this crash, he/she should know what to put in here. 0189 // This message is the only one translated in this function 0190 report.append(xi18nc("@info/plain", 0191 "<placeholder>In detail, tell us what you were doing " 0192 " when the application crashed.</placeholder>")); 0193 } 0194 report.append(QLatin1String("\n\n")); 0195 } 0196 0197 // Crash reproducibility 0198 switch (m_reproducible) { 0199 case ReproducibleUnsure: 0200 report.append(QStringLiteral("The reporter is unsure if this crash is reproducible.\n\n")); 0201 break; 0202 case ReproducibleNever: 0203 report.append(QStringLiteral("The crash does not seem to be reproducible.\n\n")); 0204 break; 0205 case ReproducibleSometimes: 0206 report.append(QStringLiteral("The crash can be reproduced sometimes.\n\n")); 0207 break; 0208 case ReproducibleEverytime: 0209 report.append(QStringLiteral("The crash can be reproduced every time.\n\n")); 0210 break; 0211 } 0212 0213 // Backtrace 0214 switch (inlineBacktrace) { 0215 case Backtrace::Complete: 0216 report.append(QStringLiteral("-- Backtrace:\n")); 0217 break; 0218 case Backtrace::Reduced: 0219 report.append(QStringLiteral("-- Backtrace (Reduced):\n")); 0220 break; 0221 case Backtrace::Exclude: 0222 report.append(QStringLiteral("The backtrace was excluded and likely attached as a file.\n")); 0223 break; 0224 } 0225 if (!m_backtrace.isEmpty()) { 0226 switch (inlineBacktrace) { 0227 case Backtrace::Complete: 0228 report.append(m_backtrace.trimmed() + QLatin1Char('\n')); 0229 break; 0230 case Backtrace::Reduced: 0231 report.append(DrKonqi::debuggerManager()->backtraceGenerator()->parser()->simplifiedBacktrace() + QLatin1Char('\n')); 0232 break; 0233 case Backtrace::Exclude: 0234 report.append(QStringLiteral("The backtrace is attached as a comment due to length constraints\n")); 0235 break; 0236 } 0237 } else { 0238 report.append(QStringLiteral("A useful backtrace could not be generated\n")); 0239 } 0240 0241 // Possible duplicates (selected by the user) 0242 if (!m_possibleDuplicates.isEmpty()) { 0243 report.append(QLatin1Char('\n')); 0244 QString duplicatesString; 0245 for (const QString &dupe : std::as_const(m_possibleDuplicates)) { 0246 duplicatesString += QLatin1String("bug ") + dupe + QLatin1String(", "); 0247 } 0248 duplicatesString = duplicatesString.left(duplicatesString.length() - 2) + QLatin1Char('.'); 0249 report.append(QStringLiteral("The reporter indicates this bug may be a duplicate of or related to %1\n").arg(duplicatesString)); 0250 } 0251 0252 // Several possible duplicates (by bugzilla query) 0253 if (!m_allPossibleDuplicatesByQuery.isEmpty()) { 0254 report.append(QLatin1Char('\n')); 0255 QString duplicatesString; 0256 int count = m_allPossibleDuplicatesByQuery.count(); 0257 for (int i = 0; i < count && i < 5; i++) { 0258 duplicatesString += QLatin1String("bug ") + m_allPossibleDuplicatesByQuery.at(i) + QLatin1String(", "); 0259 } 0260 duplicatesString = duplicatesString.left(duplicatesString.length() - 2) + QLatin1Char('.'); 0261 report.append(QStringLiteral("Possible duplicates by query: %1\n").arg(duplicatesString)); 0262 } 0263 0264 switch (stamp) { 0265 case DrKonqiStamp::Include: { 0266 report.append(QLatin1String("\nReported using DrKonqi")); 0267 const QString product = m_productMapping->bugzillaProduct(); 0268 const QString originalProduct = m_productMapping->bugzillaProductOriginal(); 0269 if (!originalProduct.isEmpty()) { 0270 report.append( 0271 QStringLiteral( 0272 "\nThis report was filed against '%1' because the product '%2' could not be located in Bugzilla. Add it to drkonqi's mappings file!") 0273 .arg(product, originalProduct)); 0274 } 0275 break; 0276 } 0277 case DrKonqiStamp::Exclude: 0278 break; 0279 } 0280 0281 return report; 0282 } 0283 0284 QString ReportInterface::generateAttachmentComment() const 0285 { 0286 // Note: no translations should be done in this function's strings 0287 0288 const CrashedApplication *crashedApp = DrKonqi::crashedApplication(); 0289 const SystemInformation *sysInfo = DrKonqi::systemInformation(); 0290 0291 QString comment; 0292 0293 // Program name and versions 0294 comment.append(QStringLiteral("%1 (%2) using Qt %4\n\n").arg(crashedApp->fakeExecutableBaseName(), crashedApp->version(), sysInfo->qtVersion())); 0295 0296 // Details of the crash situation 0297 if (isBugAwarenessPageDataUseful()) { 0298 comment.append(QStringLiteral("%1\n\n").arg(m_reportDetailText.trimmed())); 0299 } 0300 0301 // Backtrace (only 6 lines) 0302 comment.append(QStringLiteral("-- Backtrace (Reduced):\n")); 0303 QString reducedBacktrace = DrKonqi::debuggerManager()->backtraceGenerator()->parser()->simplifiedBacktrace(); 0304 comment.append(reducedBacktrace.trimmed()); 0305 0306 return comment; 0307 } 0308 0309 Bugzilla::NewBug ReportInterface::newBugReportTemplate() const 0310 { 0311 const SystemInformation *sysInfo = DrKonqi::systemInformation(); 0312 0313 Bugzilla::NewBug bug; 0314 bug.product = m_productMapping->bugzillaProduct(); 0315 bug.component = m_productMapping->bugzillaComponent(); 0316 bug.version = m_productMapping->bugzillaVersion(); 0317 bug.op_sys = sysInfo->bugzillaOperatingSystem(); 0318 if (sysInfo->compiledSources()) { 0319 bug.platform = QLatin1String("Compiled Sources"); 0320 } else { 0321 bug.platform = sysInfo->bugzillaPlatform(); 0322 } 0323 bug.keywords = QStringList{QStringLiteral("drkonqi")}; 0324 bug.priority = QLatin1String("NOR"); 0325 bug.severity = QLatin1String("crash"); 0326 bug.summary = m_reportTitle; 0327 0328 return bug; 0329 } 0330 0331 void ReportInterface::sendCrashEvent() 0332 { 0333 #if WITH_SENTRY 0334 if (DrKonqi::debuggerManager()->backtraceGenerator()->state() == BacktraceGenerator::Loaded) { 0335 m_sentryBeacon.sendEvent(); 0336 return; 0337 } 0338 static bool connected = false; 0339 if (!connected) { 0340 connected = true; 0341 connect(DrKonqi::debuggerManager()->backtraceGenerator(), &BacktraceGenerator::done, this, [this] { 0342 m_sentryBeacon.sendEvent(); 0343 }); 0344 } 0345 if (DrKonqi::debuggerManager()->backtraceGenerator()->state() != BacktraceGenerator::Loading) { 0346 DrKonqi::debuggerManager()->backtraceGenerator()->start(); 0347 } 0348 #endif 0349 } 0350 0351 void ReportInterface::sendCrashComment() 0352 { 0353 #if WITH_SENTRY 0354 m_sentryBeacon.sendUserFeedback(m_reportTitle + QLatin1Char('\n') + m_reportDetailText + QLatin1Char('\n') + DrKonqi::kdeBugzillaURL() 0355 + QLatin1String("show_bug.cgi?id=%1").arg(QString::number(m_sentReport))); 0356 #endif 0357 } 0358 0359 void ReportInterface::sendBugReport() 0360 { 0361 sendCrashEvent(); 0362 0363 if (m_attachToBugNumber > 0) { 0364 // We are going to attach the report to an existent one 0365 connect(m_bugzillaManager, &BugzillaManager::addMeToCCFinished, this, &ReportInterface::attachBacktraceWithReport); 0366 connect(m_bugzillaManager, &BugzillaManager::addMeToCCError, this, &ReportInterface::sendReportError); 0367 // First add the user to the CC list, then attach 0368 m_bugzillaManager->addMeToCC(m_attachToBugNumber); 0369 } else { 0370 // Creating a new bug report 0371 bool attach = false; 0372 Bugzilla::NewBug report = newBugReportTemplate(); 0373 report.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Complete); 0374 0375 // If the report is too long try to reduce it, try to not include the 0376 // backtrace and eventually give up. 0377 // Bugzilla has a hard-limit on the server side, if we cannot strip the 0378 // report down enough the submission will simply not work. 0379 // Exhausting the cap with just user input is nigh impossible, so we'll 0380 // forego handling of the report being too long even without without 0381 // backtrace. 0382 // https://bugs.kde.org/show_bug.cgi?id=248807 0383 if (report.description.size() >= s_maxReportSize) { 0384 report.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Reduced); 0385 attach = true; 0386 } 0387 if (report.description.size() >= s_maxReportSize) { 0388 report.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Exclude); 0389 attach = true; 0390 } 0391 Q_ASSERT(!report.description.isEmpty()); 0392 0393 connect(m_bugzillaManager, &BugzillaManager::sendReportErrorInvalidValues, this, &ReportInterface::sendUsingDefaultProduct); 0394 connect(m_bugzillaManager, &BugzillaManager::reportSent, this, [=](int bugId) { 0395 if (attach) { 0396 m_attachToBugNumber = bugId; 0397 attachBacktrace(QStringLiteral("DrKonqi auto-attaching complete backtrace.")); 0398 } else { 0399 m_sentReport = bugId; 0400 sendCrashComment(); 0401 maybeDone(); 0402 } 0403 }); 0404 connect(m_bugzillaManager, &BugzillaManager::sendReportError, this, &ReportInterface::sendReportError); 0405 m_bugzillaManager->sendReport(report); 0406 } 0407 } 0408 0409 void ReportInterface::sendUsingDefaultProduct() const 0410 { 0411 // Fallback function: if some of the custom values fail, we need to reset all the fields to the default 0412 //(and valid) bugzilla values; and try to resend 0413 Bugzilla::NewBug bug = newBugReportTemplate(); 0414 bug.product = QLatin1String("kde"); 0415 bug.component = QLatin1String("general"); 0416 bug.platform = QLatin1String("unspecified"); 0417 bug.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Complete); 0418 m_bugzillaManager->sendReport(bug); 0419 } 0420 0421 void ReportInterface::attachBacktraceWithReport() 0422 { 0423 attachBacktrace(generateAttachmentComment()); 0424 } 0425 0426 void ReportInterface::attachBacktrace(const QString &comment) 0427 { 0428 // The user was added to the CC list, proceed with the attachment 0429 connect(m_bugzillaManager, &BugzillaManager::attachToReportSent, this, &ReportInterface::attachSent); 0430 connect(m_bugzillaManager, &BugzillaManager::attachToReportError, this, &ReportInterface::sendReportError); 0431 0432 QString reportText = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Complete); 0433 QString filename = getSuggestedKCrashFilename(DrKonqi::crashedApplication()); 0434 QLatin1String summary("New crash information added by DrKonqi"); 0435 0436 // Attach the report. The comment of the attachment also includes the bug description 0437 m_bugzillaManager->attachTextToReport(reportText, filename, summary, m_attachToBugNumber, comment); 0438 } 0439 0440 void ReportInterface::attachSent(int attachId) 0441 { 0442 Q_UNUSED(attachId); 0443 0444 // The bug was attached, consider it "sent" 0445 m_sentReport = attachId; 0446 sendCrashComment(); 0447 maybeDone(); 0448 } 0449 0450 QStringList ReportInterface::relatedBugzillaProducts() const 0451 { 0452 return m_productMapping->relatedBugzillaProducts(); 0453 } 0454 0455 bool ReportInterface::isWorthReporting() const 0456 { 0457 if (DrKonqi::ignoreQuality()) { 0458 return true; 0459 } 0460 0461 // Evaluate if the provided information is useful enough to enable the automatic report 0462 bool needToReport = false; 0463 0464 if (!m_userRememberCrashSituation) { 0465 // This should never happen... but... 0466 return false; 0467 } 0468 0469 int rating = selectedOptionsRating(); 0470 0471 BacktraceParser::Usefulness use = DrKonqi::debuggerManager()->backtraceGenerator()->parser()->backtraceUsefulness(); 0472 0473 switch (use) { 0474 case BacktraceParser::ReallyUseful: { 0475 // Perfect backtrace: require at least one option or a 100%-50% reproducible crash 0476 needToReport = (rating >= 2) || (m_reproducible == ReproducibleEverytime || m_reproducible == ReproducibleSometimes); 0477 break; 0478 } 0479 case BacktraceParser::MayBeUseful: { 0480 // Not perfect backtrace: require at least two options or a 100% reproducible crash 0481 needToReport = (rating >= 3) || (m_reproducible == ReproducibleEverytime); 0482 break; 0483 } 0484 case BacktraceParser::ProbablyUseless: 0485 // Bad backtrace: require at least two options and always reproducible (strict) 0486 needToReport = (rating >= 5) && (m_reproducible == ReproducibleEverytime); 0487 break; 0488 case BacktraceParser::Useless: 0489 case BacktraceParser::InvalidUsefulness: { 0490 needToReport = false; 0491 } 0492 } 0493 0494 return needToReport; 0495 } 0496 0497 void ReportInterface::setAttachToBugNumber(uint bugNumber) 0498 { 0499 // If bugNumber>0, the report is going to be attached to bugNumber 0500 m_attachToBugNumber = bugNumber; 0501 Q_EMIT attachToBugNumberChanged(); 0502 } 0503 0504 uint ReportInterface::attachToBugNumber() const 0505 { 0506 return m_attachToBugNumber; 0507 } 0508 0509 void ReportInterface::setDuplicateId(uint duplicate) 0510 { 0511 m_duplicate = duplicate; 0512 Q_EMIT duplicateIdChanged(); 0513 } 0514 0515 uint ReportInterface::duplicateId() const 0516 { 0517 return m_duplicate; 0518 } 0519 0520 void ReportInterface::setPossibleDuplicatesByQuery(const QStringList &list) 0521 { 0522 m_allPossibleDuplicatesByQuery = list; 0523 } 0524 0525 BugzillaManager *ReportInterface::bugzillaManager() const 0526 { 0527 return m_bugzillaManager; 0528 } 0529 0530 ProductMapping *ReportInterface::productMapping() const 0531 { 0532 return m_productMapping; 0533 } 0534 0535 void ReportInterface::maybeDone() 0536 { 0537 if (m_sentReport != 0 && m_sentryEventSent && m_sentryUserFeedbackSent) { 0538 Q_EMIT done(); 0539 } 0540 };