1 /*
2 SPDX-FileCopyrightText: 2002 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
3 SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
4 SPDX-FileCopyrightText: 2006, 2008 Vladimir Prus <ghost@cs.msu.su>
5 SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
6 SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com>
7
8 SPDX-License-Identifier: LGPL-2.0-or-later
9 */
10
11 #include "mibreakpointcontroller.h"
12
13 #include "debuglog.h"
14 #include "midebugsession.h"
15 #include "mi/micommand.h"
16 #include "stringhelpers.h"
17
18 #include <debugger/breakpoint/breakpoint.h>
19 #include <debugger/breakpoint/breakpointmodel.h>
20
21 #include <KLocalizedString>
22
23 using namespace KDevMI;
24 using namespace KDevMI::MI;
25 using namespace KDevelop;
26
27 struct MIBreakpointController::Handler : public MICommandHandler
28 {
HandlerMIBreakpointController::Handler29 Handler(MIBreakpointController* controller, const BreakpointDataPtr& b,
30 BreakpointModel::ColumnFlags columns)
31 : controller(controller)
32 , breakpoint(b)
33 , columns(columns)
34 {
35 breakpoint->sent |= columns;
36 breakpoint->dirty &= ~columns;
37 }
38
handleMIBreakpointController::Handler39 void handle(const ResultRecord& r) override
40 {
41 breakpoint->sent &= ~columns;
42
43 if (r.reason == QLatin1String("error")) {
44 breakpoint->errors |= columns;
45
46 int row = controller->breakpointRow(breakpoint);
47 if (row >= 0) {
48 controller->updateErrorText(row, r[QStringLiteral("msg")].literal());
49 qCWarning(DEBUGGERCOMMON) << r[QStringLiteral("msg")].literal();
50 }
51 } else {
52 if (breakpoint->errors & columns) {
53 breakpoint->errors &= ~columns;
54
55 if (breakpoint->errors) {
56 // Since at least one error column cleared, it's possible that any remaining
57 // error bits were collateral damage; try resending the corresponding columns
58 // to see whether errors remain.
59 breakpoint->dirty |= (breakpoint->errors & ~breakpoint->sent);
60 }
61 }
62 }
63 }
64
handlesErrorMIBreakpointController::Handler65 bool handlesError() override
66 {
67 return true;
68 }
69
70 MIBreakpointController* controller;
71 BreakpointDataPtr breakpoint;
72 BreakpointModel::ColumnFlags columns;
73 };
74
75 struct MIBreakpointController::UpdateHandler : public MIBreakpointController::Handler
76 {
UpdateHandlerMIBreakpointController::UpdateHandler77 UpdateHandler(MIBreakpointController* c, const BreakpointDataPtr& b,
78 BreakpointModel::ColumnFlags columns)
79 : Handler(c, b, columns) {}
80
handleMIBreakpointController::UpdateHandler81 void handle(const ResultRecord &r) override
82 {
83 Handler::handle(r);
84
85 int row = controller->breakpointRow(breakpoint);
86 if (row >= 0) {
87 // Note: send further updates even if we got an error; who knows: perhaps
88 // these additional updates will "unstuck" the error condition.
89 if (breakpoint->sent == 0 && breakpoint->dirty != 0) {
90 controller->sendUpdates(row);
91 }
92 controller->recalculateState(row);
93 }
94 }
95 };
96
97 struct MIBreakpointController::InsertedHandler : public MIBreakpointController::Handler
98 {
InsertedHandlerMIBreakpointController::InsertedHandler99 InsertedHandler(MIBreakpointController* c, const BreakpointDataPtr& b,
100 BreakpointModel::ColumnFlags columns)
101 : Handler(c, b, columns) {}
102
handleMIBreakpointController::InsertedHandler103 void handle(const ResultRecord &r) override
104 {
105 Handler::handle(r);
106
107 int row = controller->breakpointRow(breakpoint);
108
109 if (r.reason != QLatin1String("error")) {
110 QString bkptKind;
111 for (auto& kind : {QStringLiteral("bkpt"), QStringLiteral("wpt"), QStringLiteral("hw-rwpt"), QStringLiteral("hw-awpt")}) {
112 if (r.hasField(kind)) {
113 bkptKind = kind;
114 break;
115 }
116 }
117 if (bkptKind.isEmpty()) {
118 qCWarning(DEBUGGERCOMMON) << "Gdb sent unknown breakpoint kind";
119 return;
120 }
121
122 const Value& miBkpt = r[bkptKind];
123
124 breakpoint->debuggerId = miBkpt[QStringLiteral("number")].toInt();
125
126 if (row >= 0) {
127 controller->updateFromDebugger(row, miBkpt);
128 if (breakpoint->dirty != 0)
129 controller->sendUpdates(row);
130 } else {
131 // breakpoint was deleted while insertion was in flight
132 controller->debugSession()->addCommand(BreakDelete,
133 QString::number(breakpoint->debuggerId),
134 CmdImmediately);
135 }
136 }
137
138 if (row >= 0) {
139 controller->recalculateState(row);
140 }
141 }
142 };
143
144 struct MIBreakpointController::DeleteHandler : MIBreakpointController::Handler {
DeleteHandlerMIBreakpointController::DeleteHandler145 DeleteHandler(MIBreakpointController* c, const BreakpointDataPtr& b)
146 : Handler(c, b, BreakpointModel::ColumnFlags()) {}
147
handleMIBreakpointController::DeleteHandler148 void handle(const ResultRecord&) override
149 {
150 controller->m_pendingDeleted.removeAll(breakpoint);
151 }
152 };
153
154 struct MIBreakpointController::IgnoreChanges {
IgnoreChangesMIBreakpointController::IgnoreChanges155 explicit IgnoreChanges(MIBreakpointController& controller)
156 : controller(controller)
157 {
158 ++controller.m_ignoreChanges;
159 }
160
~IgnoreChangesMIBreakpointController::IgnoreChanges161 ~IgnoreChanges()
162 {
163 --controller.m_ignoreChanges;
164 }
165
166 MIBreakpointController& controller;
167
168 private:
169 Q_DISABLE_COPY(IgnoreChanges)
170 };
171
MIBreakpointController(MIDebugSession * parent)172 MIBreakpointController::MIBreakpointController(MIDebugSession * parent)
173 : IBreakpointController(parent)
174 {
175 Q_ASSERT(parent);
176 connect(parent, &MIDebugSession::inferiorStopped,
177 this, &MIBreakpointController::programStopped);
178
179 int numBreakpoints = breakpointModel()->breakpoints().size();
180 for (int row = 0; row < numBreakpoints; ++row)
181 breakpointAdded(row);
182 }
183
debugSession() const184 MIDebugSession *MIBreakpointController::debugSession() const
185 {
186 Q_ASSERT(QObject::parent());
187 return static_cast<MIDebugSession *>(const_cast<QObject*>(QObject::parent()));
188 }
189
breakpointRow(const BreakpointDataPtr & breakpoint)190 int MIBreakpointController::breakpointRow(const BreakpointDataPtr& breakpoint)
191 {
192 return m_breakpoints.indexOf(breakpoint);
193 }
194
setDeleteDuplicateBreakpoints(bool enable)195 void MIBreakpointController::setDeleteDuplicateBreakpoints(bool enable)
196 {
197 m_deleteDuplicateBreakpoints = enable;
198 }
199
initSendBreakpoints()200 void MIBreakpointController::initSendBreakpoints()
201 {
202 for (int row = 0; row < m_breakpoints.size(); ++row) {
203 BreakpointDataPtr breakpoint = m_breakpoints[row];
204 if (breakpoint->debuggerId < 0 && breakpoint->sent == 0) {
205 createBreakpoint(row);
206 }
207 }
208 }
209
breakpointAdded(int row)210 void MIBreakpointController::breakpointAdded(int row)
211 {
212 if (m_ignoreChanges > 0)
213 return;
214
215 auto breakpoint = BreakpointDataPtr::create();
216 m_breakpoints.insert(row, breakpoint);
217
218 const Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row);
219 if (!modelBreakpoint->enabled())
220 breakpoint->dirty |= BreakpointModel::EnableColumnFlag;
221 if (!modelBreakpoint->condition().isEmpty())
222 breakpoint->dirty |= BreakpointModel::ConditionColumnFlag;
223 if (modelBreakpoint->ignoreHits() != 0)
224 breakpoint->dirty |= BreakpointModel::IgnoreHitsColumnFlag;
225 if (!modelBreakpoint->address().isEmpty())
226 breakpoint->dirty |= BreakpointModel::LocationColumnFlag;
227
228 createBreakpoint(row);
229 }
230
breakpointModelChanged(int row,BreakpointModel::ColumnFlags columns)231 void MIBreakpointController::breakpointModelChanged(int row, BreakpointModel::ColumnFlags columns)
232 {
233 if (m_ignoreChanges > 0)
234 return;
235
236 BreakpointDataPtr breakpoint = m_breakpoints.at(row);
237 breakpoint->dirty |= columns &
238 (BreakpointModel::EnableColumnFlag | BreakpointModel::LocationColumnFlag |
239 BreakpointModel::ConditionColumnFlag | BreakpointModel::IgnoreHitsColumnFlag);
240
241 if (breakpoint->sent != 0) {
242 // Throttle the amount of commands we send to GDB; the response handler of currently
243 // in-flight commands will take care of sending the update.
244 // This also prevents us from sending updates while a break-create command is in-flight.
245 return;
246 }
247
248 if (breakpoint->debuggerId < 0) {
249 createBreakpoint(row);
250 } else {
251 sendUpdates(row);
252 }
253 }
254
breakpointAboutToBeDeleted(int row)255 void MIBreakpointController::breakpointAboutToBeDeleted(int row)
256 {
257 if (m_ignoreChanges > 0)
258 return;
259
260 BreakpointDataPtr breakpoint = m_breakpoints.at(row);
261 m_breakpoints.removeAt(row);
262
263 if (breakpoint->debuggerId < 0) {
264 // Two possibilities:
265 // (1) Breakpoint has never been sent to GDB, so we're done
266 // (2) Breakpoint has been sent to GDB, but we haven't received
267 // the response yet; the response handler will delete the
268 // breakpoint.
269 return;
270 }
271
272 if (debugSession()->debuggerStateIsOn(s_dbgNotStarted))
273 return;
274
275 debugSession()->addCommand(
276 BreakDelete, QString::number(breakpoint->debuggerId),
277 new DeleteHandler(this, breakpoint), CmdImmediately);
278 m_pendingDeleted << breakpoint;
279 }
280
281 // Note: despite the name, this is in fact session state changed.
debuggerStateChanged(IDebugSession::DebuggerState state)282 void MIBreakpointController::debuggerStateChanged(IDebugSession::DebuggerState state)
283 {
284 IgnoreChanges ignoreChanges(*this);
285 if (state == IDebugSession::EndedState ||
286 state == IDebugSession::NotStartedState) {
287 for (int row = 0; row < m_breakpoints.size(); ++row) {
288 updateState(row, Breakpoint::NotStartedState);
289 }
290 } else if (state == IDebugSession::StartingState) {
291 for (int row = 0; row < m_breakpoints.size(); ++row) {
292 updateState(row, Breakpoint::DirtyState);
293 }
294 }
295 }
296
createBreakpoint(int row)297 void MIBreakpointController::createBreakpoint(int row)
298 {
299 if (debugSession()->debuggerStateIsOn(s_dbgNotStarted))
300 return;
301
302 BreakpointDataPtr breakpoint = m_breakpoints.at(row);
303 Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row);
304
305 Q_ASSERT(breakpoint->debuggerId < 0 && breakpoint->sent == 0);
306
307 if (modelBreakpoint->location().isEmpty())
308 return;
309
310 if (modelBreakpoint->kind() == Breakpoint::CodeBreakpoint) {
311 QString location;
312 if (modelBreakpoint->line() != -1) {
313 location = QStringLiteral("%0:%1")
314 .arg(modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash))
315 .arg(modelBreakpoint->line() + 1);
316 } else {
317 location = modelBreakpoint->location();
318 }
319
320 if (location == QLatin1String("catch throw")) {
321 location = QStringLiteral("exception throw");
322 }
323
324 // Note: We rely on '-f' to be automatically added by the MICommand logic
325 QString arguments;
326 if (!modelBreakpoint->enabled())
327 arguments += QLatin1String("-d ");
328 if (!modelBreakpoint->condition().isEmpty())
329 arguments += QStringLiteral("-c %0 ").arg(Utils::quoteExpression(modelBreakpoint->condition()));
330 if (modelBreakpoint->ignoreHits() != 0)
331 arguments += QStringLiteral("-i %0 ").arg(modelBreakpoint->ignoreHits());
332 arguments += Utils::quoteExpression(location);
333
334 BreakpointModel::ColumnFlags sent =
335 BreakpointModel::EnableColumnFlag |
336 BreakpointModel::ConditionColumnFlag |
337 BreakpointModel::IgnoreHitsColumnFlag |
338 BreakpointModel::LocationColumnFlag;
339 debugSession()->addCommand(BreakInsert, arguments,
340 new InsertedHandler(this, breakpoint, sent),
341 CmdImmediately);
342 } else {
343 QString opt;
344 if (modelBreakpoint->kind() == Breakpoint::ReadBreakpoint)
345 opt = QStringLiteral("-r ");
346 else if (modelBreakpoint->kind() == Breakpoint::AccessBreakpoint)
347 opt = QStringLiteral("-a ");
348
349 debugSession()->addCommand(BreakWatch,
350 opt + Utils::quoteExpression(modelBreakpoint->location()),
351 new InsertedHandler(this, breakpoint,
352 BreakpointModel::LocationColumnFlag),
353 CmdImmediately);
354 }
355
356 recalculateState(row);
357 }
358
sendUpdates(int row)359 void MIBreakpointController::sendUpdates(int row)
360 {
361 if (debugSession()->debuggerStateIsOn(s_dbgNotStarted))
362 return;
363
364 BreakpointDataPtr breakpoint = m_breakpoints.at(row);
365 Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row);
366
367 Q_ASSERT(breakpoint->debuggerId >= 0 && breakpoint->sent == 0);
368
369 if (breakpoint->dirty & BreakpointModel::LocationColumnFlag) {
370 // Gdb considers locations as fixed, so delete and re-create the breakpoint
371 debugSession()->addCommand(BreakDelete,
372 QString::number(breakpoint->debuggerId), CmdImmediately);
373 breakpoint->debuggerId = -1;
374 createBreakpoint(row);
375 return;
376 }
377
378 if (breakpoint->dirty & BreakpointModel::EnableColumnFlag) {
379 debugSession()->addCommand(modelBreakpoint->enabled() ? BreakEnable : BreakDisable,
380 QString::number(breakpoint->debuggerId),
381 new UpdateHandler(this, breakpoint,
382 BreakpointModel::EnableColumnFlag),
383 CmdImmediately);
384 }
385 if (breakpoint->dirty & BreakpointModel::IgnoreHitsColumnFlag) {
386 debugSession()->addCommand(BreakAfter,
387 QStringLiteral("%0 %1").arg(breakpoint->debuggerId)
388 .arg(modelBreakpoint->ignoreHits()),
389 new UpdateHandler(this, breakpoint,
390 BreakpointModel::IgnoreHitsColumnFlag),
391 CmdImmediately);
392 }
393 if (breakpoint->dirty & BreakpointModel::ConditionColumnFlag) {
394 debugSession()->addCommand(BreakCondition,
395 QStringLiteral("%0 %1").arg(breakpoint->debuggerId)
396 .arg(modelBreakpoint->condition()),
397 new UpdateHandler(this, breakpoint,
398 BreakpointModel::ConditionColumnFlag),
399 CmdImmediately);
400 }
401
402 recalculateState(row);
403 }
404
recalculateState(int row)405 void MIBreakpointController::recalculateState(int row)
406 {
407 BreakpointDataPtr breakpoint = m_breakpoints.at(row);
408
409 if (breakpoint->errors == 0)
410 updateErrorText(row, QString());
411
412 Breakpoint::BreakpointState newState = Breakpoint::NotStartedState;
413 if (debugSession()->state() != IDebugSession::EndedState &&
414 debugSession()->state() != IDebugSession::NotStartedState) {
415 if (!debugSession()->debuggerStateIsOn(s_dbgNotStarted)) {
416 if (breakpoint->dirty == 0 && breakpoint->sent == 0) {
417 if (breakpoint->pending) {
418 newState = Breakpoint::PendingState;
419 } else {
420 newState = Breakpoint::CleanState;
421 }
422 } else {
423 newState = Breakpoint::DirtyState;
424 }
425 }
426 }
427
428 updateState(row, newState);
429 }
430
rowFromDebuggerId(int gdbId) const431 int MIBreakpointController::rowFromDebuggerId(int gdbId) const
432 {
433 for (int row = 0; row < m_breakpoints.size(); ++row) {
434 if (gdbId == m_breakpoints[row]->debuggerId)
435 return row;
436 }
437 return -1;
438 }
439
notifyBreakpointCreated(const AsyncRecord & r)440 void MIBreakpointController::notifyBreakpointCreated(const AsyncRecord& r)
441 {
442 const Value& miBkpt = r[QStringLiteral("bkpt")];
443
444 // Breakpoints with multiple locations are represented by a parent breakpoint (e.g. 1)
445 // and multiple child breakpoints (e.g. 1.1, 1.2, 1.3, ...).
446 // We ignore the child breakpoints here in the current implementation; this can lead to dubious
447 // results in the UI when breakpoints are marked in document views (e.g. when a breakpoint
448 // applies to multiple overloads of a C++ function simultaneously) and in disassembly
449 // (e.g. when a breakpoint is set in an inlined functions).
450 if (miBkpt[QStringLiteral("number")].literal().contains(QLatin1Char('.')))
451 return;
452
453 createFromDebugger(miBkpt);
454 }
455
notifyBreakpointModified(const AsyncRecord & r)456 void MIBreakpointController::notifyBreakpointModified(const AsyncRecord& r)
457 {
458 const Value& miBkpt = r[QStringLiteral("bkpt")];
459 const int gdbId = miBkpt[QStringLiteral("number")].toInt();
460 const int row = rowFromDebuggerId(gdbId);
461
462 if (row < 0) {
463 for (const auto& breakpoint : qAsConst(m_pendingDeleted)) {
464 if (breakpoint->debuggerId == gdbId) {
465 // Received a modification of a breakpoint whose deletion is currently
466 // in-flight; simply ignore it.
467 return;
468 }
469 }
470
471 qCWarning(DEBUGGERCOMMON) << "Received a modification of an unknown breakpoint";
472 createFromDebugger(miBkpt);
473 } else {
474 updateFromDebugger(row, miBkpt);
475 }
476 }
477
notifyBreakpointDeleted(const AsyncRecord & r)478 void MIBreakpointController::notifyBreakpointDeleted(const AsyncRecord& r)
479 {
480 const int gdbId = r[QStringLiteral("id")].toInt();
481 const int row = rowFromDebuggerId(gdbId);
482
483 if (row < 0) {
484 // The user may also have deleted the breakpoint via the UI simultaneously
485 return;
486 }
487
488 IgnoreChanges ignoreChanges(*this);
489 breakpointModel()->removeRow(row);
490 m_breakpoints.removeAt(row);
491 }
492
createFromDebugger(const Value & miBkpt)493 void MIBreakpointController::createFromDebugger(const Value& miBkpt)
494 {
495 const QString type = miBkpt[QStringLiteral("type")].literal();
496 Breakpoint::BreakpointKind gdbKind;
497 if (type == QLatin1String("breakpoint") || type == QLatin1String("catchpoint")) {
498 gdbKind = Breakpoint::CodeBreakpoint;
499 } else if (type == QLatin1String("watchpoint") || type == QLatin1String("hw watchpoint")) {
500 gdbKind = Breakpoint::WriteBreakpoint;
501 } else if (type == QLatin1String("read watchpoint")) {
502 gdbKind = Breakpoint::ReadBreakpoint;
503 } else if (type == QLatin1String("acc watchpoint")) {
504 gdbKind = Breakpoint::AccessBreakpoint;
505 } else {
506 qCWarning(DEBUGGERCOMMON) << "Unknown breakpoint type " << type;
507 return;
508 }
509
510 // During debugger startup, we want to avoid creating duplicate breakpoints when the same breakpoint
511 // appears both in our model and in a init file e.g. .gdbinit
512 BreakpointModel* model = breakpointModel();
513 const int numRows = model->rowCount();
514 for (int row = 0; row < numRows; ++row) {
515 BreakpointDataPtr breakpoint = m_breakpoints.at(row);
516 const bool breakpointSent = breakpoint->debuggerId >= 0 || breakpoint->sent != 0;
517 if (breakpointSent && !m_deleteDuplicateBreakpoints)
518 continue;
519
520 Breakpoint* modelBreakpoint = model->breakpoint(row);
521 if (modelBreakpoint->kind() != gdbKind)
522 continue;
523
524 if (gdbKind == Breakpoint::CodeBreakpoint) {
525 bool sameLocation = false;
526
527 if (miBkpt.hasField(QStringLiteral("fullname")) && miBkpt.hasField(QStringLiteral("line"))) {
528 const QString location = Utils::unquoteExpression(miBkpt[QStringLiteral("fullname")].literal());
529 const int line = miBkpt[QStringLiteral("line")].toInt() - 1;
530 if (location == modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash) &&
531 line == modelBreakpoint->line())
532 {
533 sameLocation = true;
534 }
535 }
536
537 if (!sameLocation && miBkpt.hasField(QStringLiteral("original-location"))) {
538 const QString location = miBkpt[QStringLiteral("original-location")].literal();
539 if (location == modelBreakpoint->location()) {
540 sameLocation = true;
541 } else {
542 QRegExp rx(QStringLiteral("^(.+):(\\d+)$"));
543 if (rx.indexIn(location) != -1 &&
544 Utils::unquoteExpression(rx.cap(1)) == modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash) &&
545 rx.cap(2).toInt() - 1 == modelBreakpoint->line()) {
546 sameLocation = true;
547 }
548 }
549 }
550
551 if (!sameLocation && miBkpt.hasField(QStringLiteral("what")) && miBkpt[QStringLiteral("what")].literal() == QLatin1String("exception throw")) {
552 if (modelBreakpoint->expression() == QLatin1String("catch throw") ||
553 modelBreakpoint->expression() == QLatin1String("exception throw")) {
554 sameLocation = true;
555 }
556 }
557
558 if (!sameLocation)
559 continue;
560 } else {
561 if (Utils::unquoteExpression(miBkpt[QStringLiteral("original-location")].literal()) != modelBreakpoint->expression()) {
562 continue;
563 }
564 }
565
566 QString condition;
567 if (miBkpt.hasField(QStringLiteral("cond"))) {
568 condition = miBkpt[QStringLiteral("cond")].literal();
569 }
570 if (condition != modelBreakpoint->condition())
571 continue;
572
573 // Breakpoint is equivalent
574 if (!breakpointSent) {
575 breakpoint->debuggerId = miBkpt[QStringLiteral("number")].toInt();
576
577 // Reasonable people can probably have different opinions about what the "correct" behavior
578 // should be for the "enabled" and "ignore hits" column.
579 // Here, we let the status in KDevelop's UI take precedence, which we suspect to be
580 // marginally more useful. Dirty data will be sent during the initial sending of the
581 // breakpoint list.
582 const bool gdbEnabled = miBkpt[QStringLiteral("enabled")].literal() == QLatin1String("y");
583 if (gdbEnabled != modelBreakpoint->enabled())
584 breakpoint->dirty |= BreakpointModel::EnableColumnFlag;
585
586 int gdbIgnoreHits = 0;
587 if (miBkpt.hasField(QStringLiteral("ignore")))
588 gdbIgnoreHits = miBkpt[QStringLiteral("ignore")].toInt();
589 if (gdbIgnoreHits != modelBreakpoint->ignoreHits())
590 breakpoint->dirty |= BreakpointModel::IgnoreHitsColumnFlag;
591
592 updateFromDebugger(row, miBkpt, BreakpointModel::EnableColumnFlag | BreakpointModel::IgnoreHitsColumnFlag);
593 return;
594 }
595
596 // Breakpoint from the model has already been sent, but we want to delete duplicates
597 // It is not entirely clear _which_ breakpoint ought to be deleted, and reasonable people
598 // may have different opinions.
599 // We suspect that it is marginally more useful to delete the existing model breakpoint;
600 // after all, this only happens when a user command creates a breakpoint, and perhaps the
601 // user intends to modify the breakpoint they created manually. In any case,
602 // this situation should only happen rarely (in particular, when a user sets a breakpoint
603 // inside the remote run script).
604 model->removeRows(row, 1);
605 break; // fall through to pick up the manually created breakpoint in the model
606 }
607
608 // No equivalent breakpoint found, or we have one but want to be consistent with GDB's
609 // behavior of allowing multiple equivalent breakpoint.
610 IgnoreChanges ignoreChanges(*this);
611 const int row = m_breakpoints.size();
612 Q_ASSERT(row == model->rowCount());
613
614 switch (gdbKind) {
615 case Breakpoint::WriteBreakpoint: model->addWatchpoint(); break;
616 case Breakpoint::ReadBreakpoint: model->addReadWatchpoint(); break;
617 case Breakpoint::AccessBreakpoint: model->addAccessWatchpoint(); break;
618 case Breakpoint::CodeBreakpoint: model->addCodeBreakpoint(); break;
619 default: Q_ASSERT(false); return;
620 }
621
622 // Since we are in ignore-changes mode, we have to add the BreakpointData manually.
623 auto breakpoint = BreakpointDataPtr::create();
624 m_breakpoints << breakpoint;
625 breakpoint->debuggerId = miBkpt[QStringLiteral("number")].toInt();
626
627 updateFromDebugger(row, miBkpt);
628 }
629
630 // This method is required for the legacy interface which will be removed
sendMaybe(KDevelop::Breakpoint *)631 void MIBreakpointController::sendMaybe(KDevelop::Breakpoint*)
632 {
633 Q_ASSERT(false);
634 }
635
updateFromDebugger(int row,const Value & miBkpt,BreakpointModel::ColumnFlags lockedColumns)636 void MIBreakpointController::updateFromDebugger(int row, const Value& miBkpt, BreakpointModel::ColumnFlags lockedColumns)
637 {
638 IgnoreChanges ignoreChanges(*this);
639 BreakpointDataPtr breakpoint = m_breakpoints[row];
640 Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row);
641
642 // Commands that are currently in flight will overwrite the modification we have received,
643 // so do not update the corresponding data
644 lockedColumns |= breakpoint->sent | breakpoint->dirty;
645
646 // TODO:
647 // Gdb has a notion of "original-location", which is the "expression" or "location" used
648 // to set the breakpoint, and notions of the actual location of the breakpoint (function name,
649 // address, source file and line). The breakpoint model currently does not map well to this
650 // (though it arguably should), and does not support multi-location breakpoints at all.
651 // We try to do the best we can until the breakpoint model gets cleaned up.
652 if (miBkpt.hasField(QStringLiteral("fullname")) && miBkpt.hasField(QStringLiteral("line"))) {
653 modelBreakpoint->setLocation(
654 QUrl::fromLocalFile(Utils::unquoteExpression(miBkpt[QStringLiteral("fullname")].literal())),
655 miBkpt[QStringLiteral("line")].toInt() - 1);
656 } else if (miBkpt.hasField(QStringLiteral("original-location"))) {
657 QRegExp rx(QStringLiteral("^(.+):(\\d+)$"));
658 QString location = miBkpt[QStringLiteral("original-location")].literal();
659 if (rx.indexIn(location) != -1) {
660 modelBreakpoint->setLocation(QUrl::fromLocalFile(Utils::unquoteExpression(rx.cap(1))),
661 rx.cap(2).toInt()-1);
662 } else {
663 modelBreakpoint->setData(Breakpoint::LocationColumn, Utils::unquoteExpression(location));
664 }
665 } else if (miBkpt.hasField(QStringLiteral("what"))) {
666 modelBreakpoint->setExpression(miBkpt[QStringLiteral("what")].literal());
667 } else {
668 qCWarning(DEBUGGERCOMMON) << "Breakpoint doesn't contain required location/expression data";
669 }
670
671 if (!(lockedColumns & BreakpointModel::EnableColumnFlag)) {
672 bool enabled = true;
673 if (miBkpt.hasField(QStringLiteral("enabled"))) {
674 if (miBkpt[QStringLiteral("enabled")].literal() == QLatin1String("n"))
675 enabled = false;
676 }
677 modelBreakpoint->setData(Breakpoint::EnableColumn, enabled ? Qt::Checked : Qt::Unchecked);
678 breakpoint->dirty &= ~BreakpointModel::EnableColumnFlag;
679 }
680
681 if (!(lockedColumns & BreakpointModel::ConditionColumnFlag)) {
682 QString condition;
683 if (miBkpt.hasField(QStringLiteral("cond"))) {
684 condition = miBkpt[QStringLiteral("cond")].literal();
685 }
686 modelBreakpoint->setCondition(condition);
687 breakpoint->dirty &= ~BreakpointModel::ConditionColumnFlag;
688 }
689
690 if (!(lockedColumns & BreakpointModel::IgnoreHitsColumnFlag)) {
691 int ignoreHits = 0;
692 if (miBkpt.hasField(QStringLiteral("ignore"))) {
693 ignoreHits = miBkpt[QStringLiteral("ignore")].toInt();
694 }
695 modelBreakpoint->setIgnoreHits(ignoreHits);
696 breakpoint->dirty &= ~BreakpointModel::IgnoreHitsColumnFlag;
697 }
698
699 breakpoint->pending = false;
700 if (miBkpt.hasField(QStringLiteral("addr")) && miBkpt[QStringLiteral("addr")].literal() == QLatin1String("<PENDING>")) {
701 breakpoint->pending = true;
702 }
703
704 int hitCount = 0;
705 if (miBkpt.hasField(QStringLiteral("times"))) {
706 hitCount = miBkpt[QStringLiteral("times")].toInt();
707 }
708 updateHitCount(row, hitCount);
709
710 recalculateState(row);
711 }
712
programStopped(const AsyncRecord & r)713 void MIBreakpointController::programStopped(const AsyncRecord& r)
714 {
715 if (!r.hasField(QStringLiteral("reason")))
716 return;
717
718 const QString reason = r[QStringLiteral("reason")].literal();
719
720 int debuggerId = -1;
721 if (reason == QLatin1String("breakpoint-hit")) {
722 debuggerId = r[QStringLiteral("bkptno")].toInt();
723 } else if (reason == QLatin1String("watchpoint-trigger")) {
724 debuggerId = r[QStringLiteral("wpt")][QStringLiteral("number")].toInt();
725 } else if (reason == QLatin1String("read-watchpoint-trigger")) {
726 debuggerId = r[QStringLiteral("hw-rwpt")][QStringLiteral("number")].toInt();
727 } else if (reason == QLatin1String("access-watchpoint-trigger")) {
728 debuggerId = r[QStringLiteral("hw-awpt")][QStringLiteral("number")].toInt();
729 }
730
731 if (debuggerId < 0)
732 return;
733
734 int row = rowFromDebuggerId(debuggerId);
735 if (row < 0)
736 return;
737
738 QString msg;
739 if (r.hasField(QStringLiteral("value"))) {
740 if (r[QStringLiteral("value")].hasField(QStringLiteral("old"))) {
741 msg += i18n("<br>Old value: %1", r[QStringLiteral("value")][QStringLiteral("old")].literal());
742 }
743 if (r[QStringLiteral("value")].hasField(QStringLiteral("new"))) {
744 msg += i18n("<br>New value: %1", r[QStringLiteral("value")][QStringLiteral("new")].literal());
745 }
746 }
747
748 notifyHit(row, msg);
749 }
750