File indexing completed on 2024-04-28 04:38:33

0001 /*
0002     SPDX-FileCopyrightText: 2002 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
0003     SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
0004     SPDX-FileCopyrightText: 2006, 2008 Vladimir Prus <ghost@cs.msu.su>
0005     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
0006     SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "mibreakpointcontroller.h"
0012 
0013 #include "debuglog.h"
0014 #include "midebugsession.h"
0015 #include "mi/micommand.h"
0016 #include "stringhelpers.h"
0017 
0018 #include <debugger/breakpoint/breakpoint.h>
0019 #include <debugger/breakpoint/breakpointmodel.h>
0020 
0021 #include <KLocalizedString>
0022 
0023 using namespace KDevMI;
0024 using namespace KDevMI::MI;
0025 using namespace KDevelop;
0026 
0027 struct MIBreakpointController::Handler : public MICommandHandler
0028 {
0029     Handler(MIBreakpointController* controller, const BreakpointDataPtr& b,
0030             BreakpointModel::ColumnFlags columns)
0031         : controller(controller)
0032         , breakpoint(b)
0033         , columns(columns)
0034     {
0035         breakpoint->sent |= columns;
0036         breakpoint->dirty &= ~columns;
0037     }
0038 
0039     void handle(const ResultRecord& r) override
0040     {
0041         breakpoint->sent &= ~columns;
0042 
0043         if (r.reason == QLatin1String("error")) {
0044             breakpoint->errors |= columns;
0045 
0046             int row = controller->breakpointRow(breakpoint);
0047             if (row >= 0) {
0048                 controller->updateErrorText(row, r[QStringLiteral("msg")].literal());
0049                 qCWarning(DEBUGGERCOMMON) << r[QStringLiteral("msg")].literal();
0050             }
0051         } else {
0052             if (breakpoint->errors & columns) {
0053                 breakpoint->errors &= ~columns;
0054 
0055                 if (breakpoint->errors) {
0056                     // Since at least one error column cleared, it's possible that any remaining
0057                     // error bits were collateral damage; try resending the corresponding columns
0058                     // to see whether errors remain.
0059                     breakpoint->dirty |= (breakpoint->errors & ~breakpoint->sent);
0060                 }
0061             }
0062         }
0063     }
0064 
0065     bool handlesError() override
0066     {
0067         return true;
0068     }
0069 
0070     MIBreakpointController* controller;
0071     BreakpointDataPtr breakpoint;
0072     BreakpointModel::ColumnFlags columns;
0073 };
0074 
0075 struct MIBreakpointController::UpdateHandler : public MIBreakpointController::Handler
0076 {
0077     UpdateHandler(MIBreakpointController* c, const BreakpointDataPtr& b,
0078                   BreakpointModel::ColumnFlags columns)
0079         : Handler(c, b, columns) {}
0080 
0081     void handle(const ResultRecord &r) override
0082     {
0083         Handler::handle(r);
0084 
0085         int row = controller->breakpointRow(breakpoint);
0086         if (row >= 0) {
0087             // Note: send further updates even if we got an error; who knows: perhaps
0088             // these additional updates will "unstuck" the error condition.
0089             if (breakpoint->sent == 0 && breakpoint->dirty != 0) {
0090                 controller->sendUpdates(row);
0091             }
0092             controller->recalculateState(row);
0093         }
0094     }
0095 };
0096 
0097 struct MIBreakpointController::InsertedHandler : public MIBreakpointController::Handler
0098 {
0099     InsertedHandler(MIBreakpointController* c, const BreakpointDataPtr& b,
0100                     BreakpointModel::ColumnFlags columns)
0101         : Handler(c, b, columns) {}
0102 
0103     void handle(const ResultRecord &r) override
0104     {
0105         Handler::handle(r);
0106 
0107         int row = controller->breakpointRow(breakpoint);
0108 
0109         if (r.reason != QLatin1String("error")) {
0110             QString bkptKind;
0111             for (auto& kind : {QStringLiteral("bkpt"), QStringLiteral("wpt"), QStringLiteral("hw-rwpt"), QStringLiteral("hw-awpt")}) {
0112                 if (r.hasField(kind)) {
0113                     bkptKind = kind;
0114                     break;
0115                 }
0116             }
0117             if (bkptKind.isEmpty()) {
0118                 qCWarning(DEBUGGERCOMMON) << "Gdb sent unknown breakpoint kind";
0119                 return;
0120             }
0121 
0122             const Value& miBkpt = r[bkptKind];
0123 
0124             breakpoint->debuggerId = miBkpt[QStringLiteral("number")].toInt();
0125 
0126             if (row >= 0) {
0127                 controller->updateFromDebugger(row, miBkpt);
0128                 if (breakpoint->dirty != 0)
0129                     controller->sendUpdates(row);
0130             } else {
0131                 // breakpoint was deleted while insertion was in flight
0132                 controller->debugSession()->addCommand(BreakDelete,
0133                                                        QString::number(breakpoint->debuggerId),
0134                                                        CmdImmediately);
0135             }
0136         }
0137 
0138         if (row >= 0) {
0139             controller->recalculateState(row);
0140         }
0141     }
0142 };
0143 
0144 struct MIBreakpointController::DeleteHandler : MIBreakpointController::Handler {
0145     DeleteHandler(MIBreakpointController* c, const BreakpointDataPtr& b)
0146         : Handler(c, b, BreakpointModel::ColumnFlags()) {}
0147 
0148     void handle(const ResultRecord&) override
0149     {
0150         controller->m_pendingDeleted.removeAll(breakpoint);
0151     }
0152 };
0153 
0154 struct MIBreakpointController::IgnoreChanges {
0155     explicit IgnoreChanges(MIBreakpointController& controller)
0156         : controller(controller)
0157     {
0158         ++controller.m_ignoreChanges;
0159     }
0160 
0161     ~IgnoreChanges()
0162     {
0163         --controller.m_ignoreChanges;
0164     }
0165 
0166     MIBreakpointController& controller;
0167 
0168 private:
0169     Q_DISABLE_COPY(IgnoreChanges)
0170 };
0171 
0172 MIBreakpointController::MIBreakpointController(MIDebugSession * parent)
0173     : IBreakpointController(parent)
0174 {
0175     Q_ASSERT(parent);
0176     connect(parent, &MIDebugSession::inferiorStopped,
0177             this, &MIBreakpointController::programStopped);
0178 
0179     int numBreakpoints = breakpointModel()->breakpoints().size();
0180     for (int row = 0; row < numBreakpoints; ++row)
0181         breakpointAdded(row);
0182 }
0183 
0184 MIDebugSession *MIBreakpointController::debugSession() const
0185 {
0186     Q_ASSERT(QObject::parent());
0187     return static_cast<MIDebugSession *>(const_cast<QObject*>(QObject::parent()));
0188 }
0189 
0190 int MIBreakpointController::breakpointRow(const BreakpointDataPtr& breakpoint)
0191 {
0192     return m_breakpoints.indexOf(breakpoint);
0193 }
0194 
0195 void MIBreakpointController::setDeleteDuplicateBreakpoints(bool enable)
0196 {
0197     m_deleteDuplicateBreakpoints = enable;
0198 }
0199 
0200 void MIBreakpointController::initSendBreakpoints()
0201 {
0202     for (int row = 0; row < m_breakpoints.size(); ++row) {
0203         BreakpointDataPtr breakpoint = m_breakpoints[row];
0204         if (breakpoint->debuggerId < 0 && breakpoint->sent == 0) {
0205             createBreakpoint(row);
0206         }
0207     }
0208 }
0209 
0210 void MIBreakpointController::breakpointAdded(int row)
0211 {
0212     if (m_ignoreChanges > 0)
0213         return;
0214 
0215     auto breakpoint = BreakpointDataPtr::create();
0216     m_breakpoints.insert(row, breakpoint);
0217 
0218     const Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row);
0219     if (!modelBreakpoint->enabled())
0220         breakpoint->dirty |= BreakpointModel::EnableColumnFlag;
0221     if (!modelBreakpoint->condition().isEmpty())
0222         breakpoint->dirty |= BreakpointModel::ConditionColumnFlag;
0223     if (modelBreakpoint->ignoreHits() != 0)
0224         breakpoint->dirty |= BreakpointModel::IgnoreHitsColumnFlag;
0225     if (!modelBreakpoint->address().isEmpty())
0226         breakpoint->dirty |= BreakpointModel::LocationColumnFlag;
0227 
0228     createBreakpoint(row);
0229 }
0230 
0231 void MIBreakpointController::breakpointModelChanged(int row, BreakpointModel::ColumnFlags columns)
0232 {
0233     if (m_ignoreChanges > 0)
0234         return;
0235 
0236     BreakpointDataPtr breakpoint = m_breakpoints.at(row);
0237     breakpoint->dirty |= columns &
0238         (BreakpointModel::EnableColumnFlag | BreakpointModel::LocationColumnFlag |
0239          BreakpointModel::ConditionColumnFlag | BreakpointModel::IgnoreHitsColumnFlag);
0240 
0241     if (breakpoint->sent != 0) {
0242         // Throttle the amount of commands we send to GDB; the response handler of currently
0243         // in-flight commands will take care of sending the update.
0244         // This also prevents us from sending updates while a break-create command is in-flight.
0245         return;
0246     }
0247 
0248     if (breakpoint->debuggerId < 0) {
0249         createBreakpoint(row);
0250     } else {
0251         sendUpdates(row);
0252     }
0253 }
0254 
0255 void MIBreakpointController::breakpointAboutToBeDeleted(int row)
0256 {
0257     if (m_ignoreChanges > 0)
0258         return;
0259 
0260     BreakpointDataPtr breakpoint = m_breakpoints.at(row);
0261     m_breakpoints.removeAt(row);
0262 
0263     if (breakpoint->debuggerId < 0) {
0264         // Two possibilities:
0265         //  (1) Breakpoint has never been sent to GDB, so we're done
0266         //  (2) Breakpoint has been sent to GDB, but we haven't received
0267         //      the response yet; the response handler will delete the
0268         //      breakpoint.
0269         return;
0270     }
0271 
0272     if (debugSession()->debuggerStateIsOn(s_dbgNotStarted))
0273         return;
0274 
0275     debugSession()->addCommand(
0276             BreakDelete, QString::number(breakpoint->debuggerId),
0277             new DeleteHandler(this, breakpoint), CmdImmediately);
0278     m_pendingDeleted << breakpoint;
0279 }
0280 
0281 // Note: despite the name, this is in fact session state changed.
0282 void MIBreakpointController::debuggerStateChanged(IDebugSession::DebuggerState state)
0283 {
0284     IgnoreChanges ignoreChanges(*this);
0285     if (state == IDebugSession::EndedState ||
0286         state == IDebugSession::NotStartedState) {
0287         for (int row = 0; row < m_breakpoints.size(); ++row) {
0288             updateState(row, Breakpoint::NotStartedState);
0289         }
0290     } else if (state == IDebugSession::StartingState) {
0291         for (int row = 0; row < m_breakpoints.size(); ++row) {
0292             updateState(row, Breakpoint::DirtyState);
0293         }
0294     }
0295 }
0296 
0297 void MIBreakpointController::createBreakpoint(int row)
0298 {
0299     if (debugSession()->debuggerStateIsOn(s_dbgNotStarted))
0300         return;
0301 
0302     BreakpointDataPtr breakpoint = m_breakpoints.at(row);
0303     Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row);
0304 
0305     Q_ASSERT(breakpoint->debuggerId < 0 && breakpoint->sent == 0);
0306 
0307     if (modelBreakpoint->location().isEmpty())
0308         return;
0309 
0310     if (modelBreakpoint->kind() == Breakpoint::CodeBreakpoint) {
0311         QString location;
0312         if (modelBreakpoint->line() != -1) {
0313             location = QStringLiteral("%0:%1")
0314                 .arg(modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash))
0315                 .arg(modelBreakpoint->line() + 1);
0316         } else {
0317             location = modelBreakpoint->location();
0318         }
0319 
0320         if (location == QLatin1String("catch throw")) {
0321             location = QStringLiteral("exception throw");
0322         }
0323 
0324         // Note: We rely on '-f' to be automatically added by the MICommand logic
0325         QString arguments;
0326         if (!modelBreakpoint->enabled())
0327             arguments += QLatin1String("-d ");
0328         if (!modelBreakpoint->condition().isEmpty())
0329             arguments += QStringLiteral("-c %0 ").arg(Utils::quoteExpression(modelBreakpoint->condition()));
0330         if (modelBreakpoint->ignoreHits() != 0)
0331             arguments += QStringLiteral("-i %0 ").arg(modelBreakpoint->ignoreHits());
0332         arguments += Utils::quoteExpression(location);
0333 
0334         BreakpointModel::ColumnFlags sent =
0335             BreakpointModel::EnableColumnFlag |
0336             BreakpointModel::ConditionColumnFlag |
0337             BreakpointModel::IgnoreHitsColumnFlag |
0338             BreakpointModel::LocationColumnFlag;
0339         debugSession()->addCommand(BreakInsert, arguments,
0340                                    new InsertedHandler(this, breakpoint, sent),
0341                                    CmdImmediately);
0342     } else {
0343         QString opt;
0344         if (modelBreakpoint->kind() == Breakpoint::ReadBreakpoint)
0345             opt = QStringLiteral("-r ");
0346         else if (modelBreakpoint->kind() == Breakpoint::AccessBreakpoint)
0347             opt = QStringLiteral("-a ");
0348 
0349         debugSession()->addCommand(BreakWatch,
0350                                    opt + Utils::quoteExpression(modelBreakpoint->location()),
0351                                    new InsertedHandler(this, breakpoint,
0352                                                        BreakpointModel::LocationColumnFlag),
0353                                    CmdImmediately);
0354     }
0355 
0356     recalculateState(row);
0357 }
0358 
0359 void MIBreakpointController::sendUpdates(int row)
0360 {
0361     if (debugSession()->debuggerStateIsOn(s_dbgNotStarted))
0362         return;
0363 
0364     BreakpointDataPtr breakpoint = m_breakpoints.at(row);
0365     Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row);
0366 
0367     Q_ASSERT(breakpoint->debuggerId >= 0 && breakpoint->sent == 0);
0368 
0369     if (breakpoint->dirty & BreakpointModel::LocationColumnFlag) {
0370         // Gdb considers locations as fixed, so delete and re-create the breakpoint
0371         debugSession()->addCommand(BreakDelete,
0372                                    QString::number(breakpoint->debuggerId), CmdImmediately);
0373         breakpoint->debuggerId = -1;
0374         createBreakpoint(row);
0375         return;
0376     }
0377 
0378     if (breakpoint->dirty & BreakpointModel::EnableColumnFlag) {
0379         debugSession()->addCommand(modelBreakpoint->enabled() ? BreakEnable : BreakDisable,
0380                                    QString::number(breakpoint->debuggerId),
0381                                    new UpdateHandler(this, breakpoint,
0382                                                      BreakpointModel::EnableColumnFlag),
0383                                    CmdImmediately);
0384     }
0385     if (breakpoint->dirty & BreakpointModel::IgnoreHitsColumnFlag) {
0386         debugSession()->addCommand(BreakAfter,
0387                                    QStringLiteral("%0 %1").arg(breakpoint->debuggerId)
0388                                                    .arg(modelBreakpoint->ignoreHits()),
0389                                    new UpdateHandler(this, breakpoint,
0390                                                      BreakpointModel::IgnoreHitsColumnFlag),
0391                                    CmdImmediately);
0392     }
0393     if (breakpoint->dirty & BreakpointModel::ConditionColumnFlag) {
0394         debugSession()->addCommand(BreakCondition,
0395                                    QStringLiteral("%0 %1").arg(breakpoint->debuggerId)
0396                                                    .arg(modelBreakpoint->condition()),
0397                                    new UpdateHandler(this, breakpoint,
0398                                                      BreakpointModel::ConditionColumnFlag),
0399                                    CmdImmediately);
0400     }
0401 
0402     recalculateState(row);
0403 }
0404 
0405 void MIBreakpointController::recalculateState(int row)
0406 {
0407     BreakpointDataPtr breakpoint = m_breakpoints.at(row);
0408 
0409     if (breakpoint->errors == 0)
0410         updateErrorText(row, QString());
0411 
0412     Breakpoint::BreakpointState newState = Breakpoint::NotStartedState;
0413     if (debugSession()->state() != IDebugSession::EndedState &&
0414         debugSession()->state() != IDebugSession::NotStartedState) {
0415         if (!debugSession()->debuggerStateIsOn(s_dbgNotStarted)) {
0416             if (breakpoint->dirty == 0 && breakpoint->sent == 0) {
0417                 if (breakpoint->pending) {
0418                     newState = Breakpoint::PendingState;
0419                 } else {
0420                     newState = Breakpoint::CleanState;
0421                 }
0422             } else {
0423                 newState = Breakpoint::DirtyState;
0424             }
0425         }
0426     }
0427 
0428     updateState(row, newState);
0429 }
0430 
0431 int MIBreakpointController::rowFromDebuggerId(int gdbId) const
0432 {
0433     for (int row = 0; row < m_breakpoints.size(); ++row) {
0434         if (gdbId == m_breakpoints[row]->debuggerId)
0435             return row;
0436     }
0437     return -1;
0438 }
0439 
0440 void MIBreakpointController::notifyBreakpointCreated(const AsyncRecord& r)
0441 {
0442     const Value& miBkpt = r[QStringLiteral("bkpt")];
0443 
0444     // Breakpoints with multiple locations are represented by a parent breakpoint (e.g. 1)
0445     // and multiple child breakpoints (e.g. 1.1, 1.2, 1.3, ...).
0446     // We ignore the child breakpoints here in the current implementation; this can lead to dubious
0447     // results in the UI when breakpoints are marked in document views (e.g. when a breakpoint
0448     // applies to multiple overloads of a C++ function simultaneously) and in disassembly
0449     // (e.g. when a breakpoint is set in an inlined functions).
0450     if (miBkpt[QStringLiteral("number")].literal().contains(QLatin1Char('.')))
0451         return;
0452 
0453     createFromDebugger(miBkpt);
0454 }
0455 
0456 void MIBreakpointController::notifyBreakpointModified(const AsyncRecord& r)
0457 {
0458     const Value& miBkpt = r[QStringLiteral("bkpt")];
0459     const int gdbId = miBkpt[QStringLiteral("number")].toInt();
0460     const int row = rowFromDebuggerId(gdbId);
0461 
0462     if (row < 0) {
0463         for (const auto& breakpoint : qAsConst(m_pendingDeleted)) {
0464             if (breakpoint->debuggerId == gdbId) {
0465                 // Received a modification of a breakpoint whose deletion is currently
0466                 // in-flight; simply ignore it.
0467                 return;
0468             }
0469         }
0470 
0471         qCWarning(DEBUGGERCOMMON) << "Received a modification of an unknown breakpoint";
0472         createFromDebugger(miBkpt);
0473     } else {
0474         updateFromDebugger(row, miBkpt);
0475     }
0476 }
0477 
0478 void MIBreakpointController::notifyBreakpointDeleted(const AsyncRecord& r)
0479 {
0480     const int gdbId = r[QStringLiteral("id")].toInt();
0481     const int row = rowFromDebuggerId(gdbId);
0482 
0483     if (row < 0) {
0484         // The user may also have deleted the breakpoint via the UI simultaneously
0485         return;
0486     }
0487 
0488     IgnoreChanges ignoreChanges(*this);
0489     breakpointModel()->removeRow(row);
0490     m_breakpoints.removeAt(row);
0491 }
0492 
0493 void MIBreakpointController::createFromDebugger(const Value& miBkpt)
0494 {
0495     const QString type = miBkpt[QStringLiteral("type")].literal();
0496     Breakpoint::BreakpointKind gdbKind;
0497     if (type == QLatin1String("breakpoint") || type == QLatin1String("catchpoint")) {
0498         gdbKind = Breakpoint::CodeBreakpoint;
0499     } else if (type == QLatin1String("watchpoint") || type == QLatin1String("hw watchpoint")) {
0500         gdbKind = Breakpoint::WriteBreakpoint;
0501     } else if (type == QLatin1String("read watchpoint")) {
0502         gdbKind = Breakpoint::ReadBreakpoint;
0503     } else if (type == QLatin1String("acc watchpoint")) {
0504         gdbKind = Breakpoint::AccessBreakpoint;
0505     } else {
0506         qCWarning(DEBUGGERCOMMON) << "Unknown breakpoint type " << type;
0507         return;
0508     }
0509 
0510     // During debugger startup, we want to avoid creating duplicate breakpoints when the same breakpoint
0511     // appears both in our model and in a init file e.g. .gdbinit
0512     BreakpointModel* model = breakpointModel();
0513     const int numRows = model->rowCount();
0514     for (int row = 0; row < numRows; ++row) {
0515         BreakpointDataPtr breakpoint = m_breakpoints.at(row);
0516         const bool breakpointSent = breakpoint->debuggerId >= 0 || breakpoint->sent != 0;
0517         if (breakpointSent && !m_deleteDuplicateBreakpoints)
0518             continue;
0519 
0520         Breakpoint* modelBreakpoint = model->breakpoint(row);
0521         if (modelBreakpoint->kind() != gdbKind)
0522             continue;
0523 
0524         if (gdbKind == Breakpoint::CodeBreakpoint) {
0525             bool sameLocation = false;
0526 
0527             if (miBkpt.hasField(QStringLiteral("fullname")) && miBkpt.hasField(QStringLiteral("line"))) {
0528                 const QString location = Utils::unquoteExpression(miBkpt[QStringLiteral("fullname")].literal());
0529                 const int line = miBkpt[QStringLiteral("line")].toInt() - 1;
0530                 if (location == modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash) &&
0531                     line == modelBreakpoint->line())
0532                 {
0533                     sameLocation = true;
0534                 }
0535             }
0536 
0537             if (!sameLocation && miBkpt.hasField(QStringLiteral("original-location"))) {
0538                 const QString location = miBkpt[QStringLiteral("original-location")].literal();
0539                 if (location == modelBreakpoint->location()) {
0540                     sameLocation = true;
0541                 } else {
0542                     QRegExp rx(QStringLiteral("^(.+):(\\d+)$"));
0543                     if (rx.indexIn(location) != -1 &&
0544                         Utils::unquoteExpression(rx.cap(1)) == modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash) &&
0545                         rx.cap(2).toInt() - 1 == modelBreakpoint->line()) {
0546                         sameLocation = true;
0547                     }
0548                 }
0549             }
0550 
0551             if (!sameLocation && miBkpt.hasField(QStringLiteral("what")) && miBkpt[QStringLiteral("what")].literal() == QLatin1String("exception throw")) {
0552                 if (modelBreakpoint->expression() == QLatin1String("catch throw") ||
0553                     modelBreakpoint->expression() == QLatin1String("exception throw")) {
0554                     sameLocation = true;
0555                 }
0556             }
0557 
0558             if (!sameLocation)
0559                 continue;
0560         } else {
0561             if (Utils::unquoteExpression(miBkpt[QStringLiteral("original-location")].literal()) != modelBreakpoint->expression()) {
0562                 continue;
0563             }
0564         }
0565 
0566         QString condition;
0567         if (miBkpt.hasField(QStringLiteral("cond"))) {
0568             condition = miBkpt[QStringLiteral("cond")].literal();
0569         }
0570         if (condition != modelBreakpoint->condition())
0571             continue;
0572 
0573         // Breakpoint is equivalent
0574         if (!breakpointSent) {
0575             breakpoint->debuggerId = miBkpt[QStringLiteral("number")].toInt();
0576 
0577             // Reasonable people can probably have different opinions about what the "correct" behavior
0578             // should be for the "enabled" and "ignore hits" column.
0579             // Here, we let the status in KDevelop's UI take precedence, which we suspect to be
0580             // marginally more useful. Dirty data will be sent during the initial sending of the
0581             // breakpoint list.
0582             const bool gdbEnabled = miBkpt[QStringLiteral("enabled")].literal() == QLatin1String("y");
0583             if (gdbEnabled != modelBreakpoint->enabled())
0584                 breakpoint->dirty |= BreakpointModel::EnableColumnFlag;
0585 
0586             int gdbIgnoreHits = 0;
0587             if (miBkpt.hasField(QStringLiteral("ignore")))
0588                 gdbIgnoreHits = miBkpt[QStringLiteral("ignore")].toInt();
0589             if (gdbIgnoreHits != modelBreakpoint->ignoreHits())
0590                 breakpoint->dirty |= BreakpointModel::IgnoreHitsColumnFlag;
0591 
0592             updateFromDebugger(row, miBkpt, BreakpointModel::EnableColumnFlag | BreakpointModel::IgnoreHitsColumnFlag);
0593             return;
0594         }
0595 
0596         // Breakpoint from the model has already been sent, but we want to delete duplicates
0597         // It is not entirely clear _which_ breakpoint ought to be deleted, and reasonable people
0598         // may have different opinions.
0599         // We suspect that it is marginally more useful to delete the existing model breakpoint;
0600         // after all, this only happens when a user command creates a breakpoint, and perhaps the
0601         // user intends to modify the breakpoint they created manually. In any case,
0602         // this situation should only happen rarely (in particular, when a user sets a breakpoint
0603         // inside the remote run script).
0604         model->removeRows(row, 1);
0605         break; // fall through to pick up the manually created breakpoint in the model
0606     }
0607 
0608     // No equivalent breakpoint found, or we have one but want to be consistent with GDB's
0609     // behavior of allowing multiple equivalent breakpoint.
0610     IgnoreChanges ignoreChanges(*this);
0611     const int row = m_breakpoints.size();
0612     Q_ASSERT(row == model->rowCount());
0613 
0614     switch (gdbKind) {
0615     case Breakpoint::WriteBreakpoint: model->addWatchpoint(); break;
0616     case Breakpoint::ReadBreakpoint: model->addReadWatchpoint(); break;
0617     case Breakpoint::AccessBreakpoint: model->addAccessWatchpoint(); break;
0618     case Breakpoint::CodeBreakpoint: model->addCodeBreakpoint(); break;
0619     default: Q_ASSERT(false); return;
0620     }
0621 
0622     // Since we are in ignore-changes mode, we have to add the BreakpointData manually.
0623     auto breakpoint = BreakpointDataPtr::create();
0624     m_breakpoints << breakpoint;
0625     breakpoint->debuggerId = miBkpt[QStringLiteral("number")].toInt();
0626 
0627     updateFromDebugger(row, miBkpt);
0628 }
0629 
0630 // This method is required for the legacy interface which will be removed
0631 void MIBreakpointController::sendMaybe(KDevelop::Breakpoint*)
0632 {
0633     Q_ASSERT(false);
0634 }
0635 
0636 void MIBreakpointController::updateFromDebugger(int row, const Value& miBkpt, BreakpointModel::ColumnFlags lockedColumns)
0637 {
0638     IgnoreChanges ignoreChanges(*this);
0639     BreakpointDataPtr breakpoint = m_breakpoints[row];
0640     Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row);
0641 
0642     // Commands that are currently in flight will overwrite the modification we have received,
0643     // so do not update the corresponding data
0644     lockedColumns |= breakpoint->sent | breakpoint->dirty;
0645 
0646     // TODO:
0647     // Gdb has a notion of "original-location", which is the "expression" or "location" used
0648     // to set the breakpoint, and notions of the actual location of the breakpoint (function name,
0649     // address, source file and line). The breakpoint model currently does not map well to this
0650     // (though it arguably should), and does not support multi-location breakpoints at all.
0651     // We try to do the best we can until the breakpoint model gets cleaned up.
0652     if (miBkpt.hasField(QStringLiteral("fullname")) && miBkpt.hasField(QStringLiteral("line"))) {
0653         modelBreakpoint->setLocation(
0654             QUrl::fromLocalFile(Utils::unquoteExpression(miBkpt[QStringLiteral("fullname")].literal())),
0655             miBkpt[QStringLiteral("line")].toInt() - 1);
0656     } else if (miBkpt.hasField(QStringLiteral("original-location"))) {
0657         QRegExp rx(QStringLiteral("^(.+):(\\d+)$"));
0658         QString location = miBkpt[QStringLiteral("original-location")].literal();
0659         if (rx.indexIn(location) != -1) {
0660             modelBreakpoint->setLocation(QUrl::fromLocalFile(Utils::unquoteExpression(rx.cap(1))),
0661                                          rx.cap(2).toInt()-1);
0662         } else {
0663             modelBreakpoint->setData(Breakpoint::LocationColumn, Utils::unquoteExpression(location));
0664         }
0665     } else if (miBkpt.hasField(QStringLiteral("what"))) {
0666         modelBreakpoint->setExpression(miBkpt[QStringLiteral("what")].literal());
0667     } else {
0668         qCWarning(DEBUGGERCOMMON) << "Breakpoint doesn't contain required location/expression data";
0669     }
0670 
0671     if (!(lockedColumns & BreakpointModel::EnableColumnFlag)) {
0672         bool enabled = true;
0673         if (miBkpt.hasField(QStringLiteral("enabled"))) {
0674             if (miBkpt[QStringLiteral("enabled")].literal() == QLatin1String("n"))
0675                 enabled = false;
0676         }
0677         modelBreakpoint->setData(Breakpoint::EnableColumn, enabled ? Qt::Checked : Qt::Unchecked);
0678         breakpoint->dirty &= ~BreakpointModel::EnableColumnFlag;
0679     }
0680 
0681     if (!(lockedColumns & BreakpointModel::ConditionColumnFlag)) {
0682         QString condition;
0683         if (miBkpt.hasField(QStringLiteral("cond"))) {
0684             condition = miBkpt[QStringLiteral("cond")].literal();
0685         }
0686         modelBreakpoint->setCondition(condition);
0687         breakpoint->dirty &= ~BreakpointModel::ConditionColumnFlag;
0688     }
0689 
0690     if (!(lockedColumns & BreakpointModel::IgnoreHitsColumnFlag)) {
0691         int ignoreHits = 0;
0692         if (miBkpt.hasField(QStringLiteral("ignore"))) {
0693             ignoreHits = miBkpt[QStringLiteral("ignore")].toInt();
0694         }
0695         modelBreakpoint->setIgnoreHits(ignoreHits);
0696         breakpoint->dirty &= ~BreakpointModel::IgnoreHitsColumnFlag;
0697     }
0698 
0699     breakpoint->pending = false;
0700     if (miBkpt.hasField(QStringLiteral("addr")) && miBkpt[QStringLiteral("addr")].literal() == QLatin1String("<PENDING>")) {
0701         breakpoint->pending = true;
0702     }
0703 
0704     int hitCount = 0;
0705     if (miBkpt.hasField(QStringLiteral("times"))) {
0706         hitCount = miBkpt[QStringLiteral("times")].toInt();
0707     }
0708     updateHitCount(row, hitCount);
0709 
0710     recalculateState(row);
0711 }
0712 
0713 void MIBreakpointController::programStopped(const AsyncRecord& r)
0714 {
0715     if (!r.hasField(QStringLiteral("reason")))
0716         return;
0717 
0718     const QString reason = r[QStringLiteral("reason")].literal();
0719 
0720     int debuggerId = -1;
0721     if (reason == QLatin1String("breakpoint-hit")) {
0722         debuggerId = r[QStringLiteral("bkptno")].toInt();
0723     } else if (reason == QLatin1String("watchpoint-trigger")) {
0724         debuggerId = r[QStringLiteral("wpt")][QStringLiteral("number")].toInt();
0725     } else if (reason == QLatin1String("read-watchpoint-trigger")) {
0726         debuggerId = r[QStringLiteral("hw-rwpt")][QStringLiteral("number")].toInt();
0727     } else if (reason == QLatin1String("access-watchpoint-trigger")) {
0728         debuggerId = r[QStringLiteral("hw-awpt")][QStringLiteral("number")].toInt();
0729     }
0730 
0731     if (debuggerId < 0)
0732         return;
0733 
0734     int row = rowFromDebuggerId(debuggerId);
0735     if (row < 0)
0736         return;
0737 
0738     QString msg;
0739     if (r.hasField(QStringLiteral("value"))) {
0740         if (r[QStringLiteral("value")].hasField(QStringLiteral("old"))) {
0741             msg += i18n("<br>Old value: %1", r[QStringLiteral("value")][QStringLiteral("old")].literal());
0742         }
0743         if (r[QStringLiteral("value")].hasField(QStringLiteral("new"))) {
0744             msg += i18n("<br>New value: %1", r[QStringLiteral("value")][QStringLiteral("new")].literal());
0745         }
0746     }
0747 
0748     notifyHit(row, msg);
0749 }
0750 
0751 #include "moc_mibreakpointcontroller.cpp"