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