1 /*******************************************************************
2 * reportinterface.cpp
3 * SPDX-FileCopyrightText: 2009, 2010, 2011 Dario Andres Rodriguez <andresbajotierra@gmail.com>
4 * SPDX-FileCopyrightText: 2009 George Kiagiadakis <gkiagia@users.sourceforge.net>
5 * SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 *
9 ******************************************************************/
10
11 #include "reportinterface.h"
12
13 #include <KLocalizedString>
14
15 #include "applicationdetailsexamples.h"
16 #include "backtracegenerator.h"
17 #include "bugzillalib.h"
18 #include "config-drkonqi.h"
19 #include "crashedapplication.h"
20 #include "debuggermanager.h"
21 #include "drkonqi.h"
22 #include "parser/backtraceparser.h"
23 #include "productmapping.h"
24 #include "systeminformation.h"
25
26 // Max size a report may have. This is enforced in bugzilla, hardcoded, and
27 // cannot be queried through the API, so handle this client-side in a hardcoded
28 // fashion as well.
29 static int s_maxReportSize = 65535;
30
ReportInterface(QObject * parent)31 ReportInterface::ReportInterface(QObject *parent)
32 : QObject(parent)
33 , m_duplicate(0)
34 {
35 m_bugzillaManager = new BugzillaManager(KDE_BUGZILLA_URL, this);
36
37 m_productMapping = new ProductMapping(DrKonqi::crashedApplication(), m_bugzillaManager, this);
38
39 m_appDetailsExamples = new ApplicationDetailsExamples(this);
40
41 // Information the user can provide about the crash
42 m_userRememberCrashSituation = false;
43 m_reproducible = ReproducibleUnsure;
44 m_provideActionsApplicationDesktop = false;
45 m_provideUnusualBehavior = false;
46 m_provideApplicationConfigurationDetails = false;
47
48 // Do not attach the bug report to any other existent report (create a new one)
49 m_attachToBugNumber = 0;
50 }
51
setBugAwarenessPageData(bool rememberSituation,Reproducible reproducible,bool actions,bool unusual,bool configuration)52 void ReportInterface::setBugAwarenessPageData(bool rememberSituation, Reproducible reproducible, bool actions, bool unusual, bool configuration)
53 {
54 // Save the information the user can provide about the crash from the assistant page
55 m_userRememberCrashSituation = rememberSituation;
56 m_reproducible = reproducible;
57 m_provideActionsApplicationDesktop = actions;
58 m_provideUnusualBehavior = unusual;
59 m_provideApplicationConfigurationDetails = configuration;
60 }
61
isBugAwarenessPageDataUseful() const62 bool ReportInterface::isBugAwarenessPageDataUseful() const
63 {
64 // Determine if the assistant should proceed, considering the amount of information
65 // the user can provide
66 int rating = selectedOptionsRating();
67
68 // Minimum information required even for a good backtrace.
69 bool useful = m_userRememberCrashSituation && (rating >= 2 || (m_reproducible == ReproducibleSometimes || m_reproducible == ReproducibleEverytime));
70 return useful;
71 }
72
selectedOptionsRating() const73 int ReportInterface::selectedOptionsRating() const
74 {
75 // Check how many information the user can provide and generate a rating
76 int rating = 0;
77 if (m_provideActionsApplicationDesktop) {
78 rating += 3;
79 }
80 if (m_provideApplicationConfigurationDetails) {
81 rating += 2;
82 }
83 if (m_provideUnusualBehavior) {
84 rating += 1;
85 }
86 return rating;
87 }
88
backtrace() const89 QString ReportInterface::backtrace() const
90 {
91 return m_backtrace;
92 }
93
setBacktrace(const QString & backtrace)94 void ReportInterface::setBacktrace(const QString &backtrace)
95 {
96 m_backtrace = backtrace;
97 }
98
firstBacktraceFunctions() const99 QStringList ReportInterface::firstBacktraceFunctions() const
100 {
101 return m_firstBacktraceFunctions;
102 }
103
setFirstBacktraceFunctions(const QStringList & functions)104 void ReportInterface::setFirstBacktraceFunctions(const QStringList &functions)
105 {
106 m_firstBacktraceFunctions = functions;
107 }
108
title() const109 QString ReportInterface::title() const
110 {
111 return m_reportTitle;
112 }
113
setTitle(const QString & text)114 void ReportInterface::setTitle(const QString &text)
115 {
116 m_reportTitle = text;
117 }
118
setDetailText(const QString & text)119 void ReportInterface::setDetailText(const QString &text)
120 {
121 m_reportDetailText = text;
122 }
123
setPossibleDuplicates(const QStringList & list)124 void ReportInterface::setPossibleDuplicates(const QStringList &list)
125 {
126 m_possibleDuplicates = list;
127 }
128
generateReportFullText(DrKonqiStamp stamp,Backtrace inlineBacktrace) const129 QString ReportInterface::generateReportFullText(DrKonqiStamp stamp, Backtrace inlineBacktrace) const
130 {
131 // Note: no translations should be done in this function's strings
132
133 const CrashedApplication *crashedApp = DrKonqi::crashedApplication();
134 const SystemInformation *sysInfo = DrKonqi::systemInformation();
135
136 QString report;
137
138 // Program name and versions
139 report.append(QStringLiteral("Application: %1 (%2)\n").arg(crashedApp->fakeExecutableBaseName(), crashedApp->version()));
140 if (sysInfo->compiledSources()) {
141 report.append(QStringLiteral(" (Compiled from sources)\n"));
142 } else {
143 report.append(QLatin1Char('\n'));
144 }
145 report.append(QStringLiteral("Qt Version: %1\n").arg(sysInfo->qtVersion()));
146 report.append(QStringLiteral("Frameworks Version: %1\n").arg(sysInfo->frameworksVersion()));
147
148 report.append(QStringLiteral("Operating System: %1\n").arg(sysInfo->operatingSystem()));
149 report.append(QStringLiteral("Windowing System: %1\n").arg(sysInfo->windowSystem()));
150
151 // LSB output or manually selected distro
152 if (!sysInfo->distributionPrettyName().isEmpty()) {
153 report.append(QStringLiteral("Distribution: %1\n").arg(sysInfo->distributionPrettyName()));
154 } else if (!sysInfo->bugzillaPlatform().isEmpty() && sysInfo->bugzillaPlatform() != QLatin1String("unspecified")) {
155 report.append(QStringLiteral("Distribution (Platform): %1\n").arg(sysInfo->bugzillaPlatform()));
156 }
157
158 report.append(QStringLiteral("DrKonqi: %1 [%2]\n").arg(QString::fromLatin1(PROJECT_VERSION), DrKonqi::backendClassName()));
159 report.append(QLatin1Char('\n'));
160
161 // Details of the crash situation
162 if (isBugAwarenessPageDataUseful()) {
163 report.append(QStringLiteral("-- Information about the crash:\n"));
164 if (!m_reportDetailText.isEmpty()) {
165 report.append(m_reportDetailText.trimmed());
166 } else {
167 // If the user manual reports this crash, he/she should know what to put in here.
168 // This message is the only one translated in this function
169 report.append(xi18nc("@info/plain",
170 "<placeholder>In detail, tell us what you were doing "
171 " when the application crashed.</placeholder>"));
172 }
173 report.append(QLatin1String("\n\n"));
174 }
175
176 // Crash reproducibility
177 switch (m_reproducible) {
178 case ReproducibleUnsure:
179 report.append(QStringLiteral("The reporter is unsure if this crash is reproducible.\n\n"));
180 break;
181 case ReproducibleNever:
182 report.append(QStringLiteral("The crash does not seem to be reproducible.\n\n"));
183 break;
184 case ReproducibleSometimes:
185 report.append(QStringLiteral("The crash can be reproduced sometimes.\n\n"));
186 break;
187 case ReproducibleEverytime:
188 report.append(QStringLiteral("The crash can be reproduced every time.\n\n"));
189 break;
190 }
191
192 // Backtrace
193 switch (inlineBacktrace) {
194 case Backtrace::Complete:
195 report.append(QStringLiteral("-- Backtrace:\n"));
196 break;
197 case Backtrace::Reduced:
198 report.append(QStringLiteral("-- Backtrace (Reduced):\n"));
199 break;
200 case Backtrace::Exclude:
201 report.append(QStringLiteral("The backtrace was excluded and likely attached as a file.\n"));
202 break;
203 }
204 if (!m_backtrace.isEmpty()) {
205 switch (inlineBacktrace) {
206 case Backtrace::Complete:
207 report.append(m_backtrace.trimmed() + QLatin1Char('\n'));
208 break;
209 case Backtrace::Reduced:
210 report.append(DrKonqi::debuggerManager()->backtraceGenerator()->parser()->simplifiedBacktrace() + QLatin1Char('\n'));
211 break;
212 case Backtrace::Exclude:
213 report.append(QStringLiteral("The backtrace is attached as a comment due to length constraints\n"));
214 break;
215 }
216 } else {
217 report.append(QStringLiteral("A useful backtrace could not be generated\n"));
218 }
219
220 // Possible duplicates (selected by the user)
221 if (!m_possibleDuplicates.isEmpty()) {
222 report.append(QLatin1Char('\n'));
223 QString duplicatesString;
224 for (const QString &dupe : std::as_const(m_possibleDuplicates)) {
225 duplicatesString += QLatin1String("bug ") + dupe + QLatin1String(", ");
226 }
227 duplicatesString = duplicatesString.left(duplicatesString.length() - 2) + QLatin1Char('.');
228 report.append(QStringLiteral("The reporter indicates this bug may be a duplicate of or related to %1\n").arg(duplicatesString));
229 }
230
231 // Several possible duplicates (by bugzilla query)
232 if (!m_allPossibleDuplicatesByQuery.isEmpty()) {
233 report.append(QLatin1Char('\n'));
234 QString duplicatesString;
235 int count = m_allPossibleDuplicatesByQuery.count();
236 for (int i = 0; i < count && i < 5; i++) {
237 duplicatesString += QLatin1String("bug ") + m_allPossibleDuplicatesByQuery.at(i) + QLatin1String(", ");
238 }
239 duplicatesString = duplicatesString.left(duplicatesString.length() - 2) + QLatin1Char('.');
240 report.append(QStringLiteral("Possible duplicates by query: %1\n").arg(duplicatesString));
241 }
242
243 switch (stamp) {
244 case DrKonqiStamp::Include: {
245 report.append(QLatin1String("\nReported using DrKonqi"));
246 const QString product = m_productMapping->bugzillaProduct();
247 const QString originalProduct = m_productMapping->bugzillaProductOriginal();
248 if (!originalProduct.isEmpty()) {
249 report.append(
250 QStringLiteral(
251 "\nThis report was filed against '%1' because the product '%2' could not be located in Bugzilla. Add it to drkonqi's mappings file!")
252 .arg(product, originalProduct));
253 }
254 break;
255 }
256 case DrKonqiStamp::Exclude:
257 break;
258 }
259
260 return report;
261 }
262
generateAttachmentComment() const263 QString ReportInterface::generateAttachmentComment() const
264 {
265 // Note: no translations should be done in this function's strings
266
267 const CrashedApplication *crashedApp = DrKonqi::crashedApplication();
268 const SystemInformation *sysInfo = DrKonqi::systemInformation();
269
270 QString comment;
271
272 // Program name and versions
273 comment.append(QStringLiteral("%1 (%2) using Qt %4\n\n").arg(crashedApp->fakeExecutableBaseName(), crashedApp->version(), sysInfo->qtVersion()));
274
275 // Details of the crash situation
276 if (isBugAwarenessPageDataUseful()) {
277 comment.append(QStringLiteral("%1\n\n").arg(m_reportDetailText.trimmed()));
278 }
279
280 // Backtrace (only 6 lines)
281 comment.append(QStringLiteral("-- Backtrace (Reduced):\n"));
282 QString reducedBacktrace = DrKonqi::debuggerManager()->backtraceGenerator()->parser()->simplifiedBacktrace();
283 comment.append(reducedBacktrace.trimmed());
284
285 return comment;
286 }
287
newBugReportTemplate() const288 Bugzilla::NewBug ReportInterface::newBugReportTemplate() const
289 {
290 const SystemInformation *sysInfo = DrKonqi::systemInformation();
291
292 Bugzilla::NewBug bug;
293 bug.product = m_productMapping->bugzillaProduct();
294 bug.component = m_productMapping->bugzillaComponent();
295 bug.version = m_productMapping->bugzillaVersion();
296 bug.op_sys = sysInfo->bugzillaOperatingSystem();
297 if (sysInfo->compiledSources()) {
298 bug.platform = QLatin1String("Compiled Sources");
299 } else {
300 bug.platform = sysInfo->bugzillaPlatform();
301 }
302 bug.keywords = QStringList{QStringLiteral("drkonqi")};
303 bug.priority = QLatin1String("NOR");
304 bug.severity = QLatin1String("crash");
305 bug.summary = m_reportTitle;
306
307 return bug;
308 }
309
sendBugReport()310 void ReportInterface::sendBugReport()
311 {
312 if (m_attachToBugNumber > 0) {
313 // We are going to attach the report to an existent one
314 connect(m_bugzillaManager, &BugzillaManager::addMeToCCFinished, this, &ReportInterface::attachBacktraceWithReport);
315 connect(m_bugzillaManager, &BugzillaManager::addMeToCCError, this, &ReportInterface::sendReportError);
316 // First add the user to the CC list, then attach
317 m_bugzillaManager->addMeToCC(m_attachToBugNumber);
318 } else {
319 // Creating a new bug report
320 bool attach = false;
321 Bugzilla::NewBug report = newBugReportTemplate();
322 report.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Complete);
323
324 // If the report is too long try to reduce it, try to not include the
325 // backtrace and eventually give up.
326 // Bugzilla has a hard-limit on the server side, if we cannot strip the
327 // report down enough the submission will simply not work.
328 // Exhausting the cap with just user input is nigh impossible, so we'll
329 // forego handling of the report being too long even without without
330 // backtrace.
331 // https://bugs.kde.org/show_bug.cgi?id=248807
332 if (report.description.size() >= s_maxReportSize) {
333 report.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Reduced);
334 attach = true;
335 }
336 if (report.description.size() >= s_maxReportSize) {
337 report.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Exclude);
338 attach = true;
339 }
340 Q_ASSERT(!report.description.isEmpty());
341
342 connect(m_bugzillaManager, &BugzillaManager::sendReportErrorInvalidValues, this, &ReportInterface::sendUsingDefaultProduct);
343 connect(m_bugzillaManager, &BugzillaManager::reportSent, this, [=](int bugId) {
344 if (attach) {
345 m_attachToBugNumber = bugId;
346 attachBacktrace(QStringLiteral("DrKonqi auto-attaching complete backtrace."));
347 } else {
348 Q_EMIT reportSent(bugId);
349 }
350 });
351 connect(m_bugzillaManager, &BugzillaManager::sendReportError, this, &ReportInterface::sendReportError);
352 m_bugzillaManager->sendReport(report);
353 }
354 }
355
sendUsingDefaultProduct() const356 void ReportInterface::sendUsingDefaultProduct() const
357 {
358 // Fallback function: if some of the custom values fail, we need to reset all the fields to the default
359 //(and valid) bugzilla values; and try to resend
360 Bugzilla::NewBug bug = newBugReportTemplate();
361 bug.product = QLatin1String("kde");
362 bug.component = QLatin1String("general");
363 bug.platform = QLatin1String("unspecified");
364 bug.description = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Complete);
365 m_bugzillaManager->sendReport(bug);
366 }
367
attachBacktraceWithReport()368 void ReportInterface::attachBacktraceWithReport()
369 {
370 attachBacktrace(generateAttachmentComment());
371 }
372
attachBacktrace(const QString & comment)373 void ReportInterface::attachBacktrace(const QString &comment)
374 {
375 // The user was added to the CC list, proceed with the attachment
376 connect(m_bugzillaManager, &BugzillaManager::attachToReportSent, this, &ReportInterface::attachSent);
377 connect(m_bugzillaManager, &BugzillaManager::attachToReportError, this, &ReportInterface::sendReportError);
378
379 QString reportText = generateReportFullText(ReportInterface::DrKonqiStamp::Include, ReportInterface::Backtrace::Complete);
380 QString filename = getSuggestedKCrashFilename(DrKonqi::crashedApplication());
381 QLatin1String summary("New crash information added by DrKonqi");
382
383 // Attach the report. The comment of the attachment also includes the bug description
384 m_bugzillaManager->attachTextToReport(reportText, filename, summary, m_attachToBugNumber, comment);
385 }
386
attachSent(int attachId)387 void ReportInterface::attachSent(int attachId)
388 {
389 Q_UNUSED(attachId);
390
391 // The bug was attached, consider it "sent"
392 Q_EMIT reportSent(m_attachToBugNumber);
393 }
394
relatedBugzillaProducts() const395 QStringList ReportInterface::relatedBugzillaProducts() const
396 {
397 return m_productMapping->relatedBugzillaProducts();
398 }
399
isWorthReporting() const400 bool ReportInterface::isWorthReporting() const
401 {
402 if (DrKonqi::ignoreQuality()) {
403 return true;
404 }
405
406 // Evaluate if the provided information is useful enough to enable the automatic report
407 bool needToReport = false;
408
409 if (!m_userRememberCrashSituation) {
410 // This should never happen... but...
411 return false;
412 }
413
414 int rating = selectedOptionsRating();
415
416 BacktraceParser::Usefulness use = DrKonqi::debuggerManager()->backtraceGenerator()->parser()->backtraceUsefulness();
417
418 switch (use) {
419 case BacktraceParser::ReallyUseful: {
420 // Perfect backtrace: require at least one option or a 100%-50% reproducible crash
421 needToReport = (rating >= 2) || (m_reproducible == ReproducibleEverytime || m_reproducible == ReproducibleSometimes);
422 break;
423 }
424 case BacktraceParser::MayBeUseful: {
425 // Not perfect backtrace: require at least two options or a 100% reproducible crash
426 needToReport = (rating >= 3) || (m_reproducible == ReproducibleEverytime);
427 break;
428 }
429 case BacktraceParser::ProbablyUseless:
430 // Bad backtrace: require at least two options and always reproducible (strict)
431 needToReport = (rating >= 5) && (m_reproducible == ReproducibleEverytime);
432 break;
433 case BacktraceParser::Useless:
434 case BacktraceParser::InvalidUsefulness: {
435 needToReport = false;
436 }
437 }
438
439 return needToReport;
440 }
441
setAttachToBugNumber(uint bugNumber)442 void ReportInterface::setAttachToBugNumber(uint bugNumber)
443 {
444 // If bugNumber>0, the report is going to be attached to bugNumber
445 m_attachToBugNumber = bugNumber;
446 }
447
attachToBugNumber() const448 uint ReportInterface::attachToBugNumber() const
449 {
450 return m_attachToBugNumber;
451 }
452
setDuplicateId(uint duplicate)453 void ReportInterface::setDuplicateId(uint duplicate)
454 {
455 m_duplicate = duplicate;
456 }
457
duplicateId() const458 uint ReportInterface::duplicateId() const
459 {
460 return m_duplicate;
461 }
462
setPossibleDuplicatesByQuery(const QStringList & list)463 void ReportInterface::setPossibleDuplicatesByQuery(const QStringList &list)
464 {
465 m_allPossibleDuplicatesByQuery = list;
466 }
467
bugzillaManager() const468 BugzillaManager *ReportInterface::bugzillaManager() const
469 {
470 return m_bugzillaManager;
471 }
472
appDetailsExamples() const473 ApplicationDetailsExamples *ReportInterface::appDetailsExamples() const
474 {
475 return m_appDetailsExamples;
476 }
477
productMapping() const478 ProductMapping *ReportInterface::productMapping() const
479 {
480 return m_productMapping;
481 }
482