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

0001 /*
0002     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
0003     SPDX-FileCopyrightText: 2008 Vladimir Prus <ghost@cs.msu.su>
0004     SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "mivariablecontroller.h"
0010 
0011 #include "debuglog.h"
0012 #include "midebugsession.h"
0013 #include "mivariable.h"
0014 #include "mi/mi.h"
0015 #include "mi/micommand.h"
0016 #include "stringhelpers.h"
0017 
0018 #include <debugger/interfaces/iframestackmodel.h>
0019 #include <debugger/breakpoint/breakpointmodel.h>
0020 #include <debugger/variable/variablecollection.h>
0021 #include <interfaces/icore.h>
0022 #include <interfaces/idebugcontroller.h>
0023 
0024 #include <KTextEditor/Document>
0025 
0026 using namespace KDevelop;
0027 using namespace KDevMI;
0028 using namespace KDevMI::MI;
0029 using KTextEditor::Cursor;
0030 using KTextEditor::Document;
0031 using KTextEditor::Range;
0032 
0033 MIVariableController::MIVariableController(MIDebugSession *parent)
0034     : IVariableController(parent)
0035 {
0036     Q_ASSERT(parent);
0037     connect(parent, &MIDebugSession::inferiorStopped,
0038             this, &MIVariableController::programStopped);
0039     connect(parent, &MIDebugSession::stateChanged,
0040             this, &MIVariableController::stateChanged);
0041 }
0042 
0043 MIDebugSession *MIVariableController::debugSession() const
0044 {
0045     return static_cast<MIDebugSession *>(const_cast<QObject*>(QObject::parent()));
0046 }
0047 
0048 void MIVariableController::programStopped(const AsyncRecord& r)
0049 {
0050     if (debugSession()->debuggerStateIsOn(s_shuttingDown)) return;
0051 
0052     if (r.hasField(QStringLiteral("reason")) && r[QStringLiteral("reason")].literal() == QLatin1String("function-finished")
0053         && r.hasField(QStringLiteral("gdb-result-var")))
0054     {
0055         variableCollection()->watches()->addFinishResult(r[QStringLiteral("gdb-result-var")].literal());
0056     } else {
0057         variableCollection()->watches()->removeFinishResult();
0058     }
0059 }
0060 
0061 void MIVariableController::update()
0062 {
0063     qCDebug(DEBUGGERCOMMON) << "autoUpdate =" << autoUpdate();
0064     if (autoUpdate() & UpdateWatches) {
0065         variableCollection()->watches()->reinstall();
0066     }
0067 
0068    if (autoUpdate() & UpdateLocals) {
0069         updateLocals();
0070    }
0071 
0072    if ((autoUpdate() & UpdateLocals) ||
0073        ((autoUpdate() & UpdateWatches) && variableCollection()->watches()->childCount() > 0))
0074     {
0075         debugSession()->addCommand(VarUpdate, QStringLiteral("--all-values *"), this,
0076                                    &MIVariableController::handleVarUpdate);
0077     }
0078 }
0079 
0080 void MIVariableController::handleVarUpdate(const ResultRecord& r)
0081 {
0082     const Value& changed = r[QStringLiteral("changelist")];
0083     for (int i = 0; i < changed.size(); ++i)
0084     {
0085         const Value& var = changed[i];
0086         MIVariable* v = debugSession()->findVariableByVarobjName(var[QStringLiteral("name")].literal());
0087         // v can be NULL here if we've already removed locals after step,
0088         // but the corresponding -var-delete command is still in the queue.
0089         if (v) {
0090             v->handleUpdate(var);
0091         }
0092     }
0093 }
0094 
0095 class StackListArgumentsHandler : public MICommandHandler
0096 {
0097 public:
0098     explicit StackListArgumentsHandler(const QStringList& localsName)
0099         : m_localsName(localsName)
0100     {}
0101 
0102     void handle(const ResultRecord &r) override
0103     {
0104         if (!KDevelop::ICore::self()->debugController()) return; //happens on shutdown
0105 
0106         if (r.hasField(QStringLiteral("stack-args")) && r[QStringLiteral("stack-args")].size() > 0) {
0107             const Value& locals = r[QStringLiteral("stack-args")][0][QStringLiteral("args")];
0108 
0109             m_localsName.reserve(m_localsName.size() + locals.size());
0110             for (int i = 0; i < locals.size(); i++) {
0111                 m_localsName << locals[i].literal();
0112             }
0113             const QList<Variable*> variables = KDevelop::ICore::self()->debugController()->variableCollection()
0114                     ->locals()->updateLocals(m_localsName);
0115             for (Variable* v : variables) {
0116                 v->attachMaybe();
0117             }
0118         }
0119     }
0120 
0121 private:
0122     QStringList m_localsName;
0123 };
0124 
0125 class StackListLocalsHandler : public MICommandHandler
0126 {
0127 public:
0128     explicit StackListLocalsHandler(MIDebugSession *session)
0129         : m_session(session)
0130     {}
0131 
0132     void handle(const ResultRecord &r) override
0133     {
0134         if (r.hasField(QStringLiteral("locals"))) {
0135             const Value& locals = r[QStringLiteral("locals")];
0136 
0137             QStringList localsName;
0138             localsName.reserve(locals.size());
0139             for (int i = 0; i < locals.size(); i++) {
0140                 const Value& var = locals[i];
0141                 localsName << var[QStringLiteral("name")].literal();
0142             }
0143             int frame = m_session->frameStackModel()->currentFrame();
0144             m_session->addCommand(StackListArguments,
0145                                 // do not show value, low-frame, high-frame
0146                                 QStringLiteral("0 %1 %2").arg(frame).arg(frame),
0147                                 new StackListArgumentsHandler(localsName));
0148         }
0149     }
0150 
0151 private:
0152     MIDebugSession *m_session;
0153 };
0154 
0155 void MIVariableController::updateLocals()
0156 {
0157     debugSession()->addCommand(StackListLocals, QStringLiteral("--simple-values"),
0158                                new StackListLocalsHandler(debugSession()));
0159 }
0160 
0161 Range MIVariableController::expressionRangeUnderCursor(Document* doc, const Cursor& cursor)
0162 {
0163     const QString line = doc->line(cursor.line());
0164     int index = cursor.column();
0165     if (index >= line.size()) {
0166         return {};
0167     }
0168     QChar c = line[index];
0169     if (!c.isLetterOrNumber() && c != QLatin1Char('_')) {
0170         return {};
0171     }
0172 
0173     int start = Utils::expressionAt(line, index+1);
0174     int end = index;
0175     for (; end < line.size(); ++end)
0176     {
0177         QChar c = line[end];
0178         if (!(c.isLetterOrNumber() || c == QLatin1Char('_'))) {
0179             break;
0180         }
0181     }
0182     if (!(start < end))
0183         return {};
0184 
0185     return { Cursor{cursor.line(), start}, Cursor{cursor.line(), end} };
0186 }
0187 
0188 
0189 void MIVariableController::addWatch(KDevelop::Variable* variable)
0190 {
0191     // FIXME: should add async 'get full expression' method
0192     // to MIVariable, not poke at varobj. In that case,
0193     // we will be able to make addWatch a generic method, not
0194     // gdb-specific one.
0195     if (auto* gv = qobject_cast<MIVariable*>(variable)) {
0196         debugSession()->addCommand(VarInfoPathExpression,
0197                                    gv->varobj(),
0198                                    this, &MIVariableController::addWatch);
0199     }
0200 }
0201 
0202 void MIVariableController::addWatchpoint(KDevelop::Variable* variable)
0203 {
0204     // FIXME: should add async 'get full expression' method
0205     // to MIVariable, not poke at varobj. In that case,
0206     // we will be able to make addWatchpoint a generic method, not
0207     // gdb-specific one.
0208     if (auto* gv = qobject_cast<MIVariable*>(variable)) {
0209         debugSession()->addCommand(VarInfoPathExpression,
0210                                    gv->varobj(),
0211                                    this, &MIVariableController::addWatchpoint);
0212     }
0213 }
0214 
0215 void MIVariableController::addWatch(const ResultRecord& r)
0216 {
0217     if (r.reason == QLatin1String("done")
0218         && r.hasField(QStringLiteral("path_expr"))
0219         && !r[QStringLiteral("path_expr")].literal().isEmpty()) {
0220         variableCollection()->watches()->add(r[QStringLiteral("path_expr")].literal());
0221     }
0222 }
0223 
0224 void MIVariableController::addWatchpoint(const ResultRecord& r)
0225 {
0226     if (r.reason == QLatin1String("done") && !r[QStringLiteral("path_expr")].literal().isEmpty()) {
0227         KDevelop::ICore::self()->debugController()->breakpointModel()->addWatchpoint(r[QStringLiteral("path_expr")].literal());
0228     }
0229 }
0230 
0231 Variable* MIVariableController::createVariable(TreeModel* model, TreeItem* parent,
0232                                                  const QString& expression, const QString& display)
0233 {
0234     return new MIVariable(debugSession(), model, parent, expression, display);
0235 }
0236 
0237 void MIVariableController::handleEvent(IDebugSession::event_t event)
0238 {
0239     IVariableController::handleEvent(event);
0240 }
0241 
0242 void MIVariableController::stateChanged(IDebugSession::DebuggerState state)
0243 {
0244     if (state == IDebugSession::EndedState) {
0245         debugSession()->markAllVariableDead();
0246     }
0247 }
0248 
0249 #include "moc_mivariablecontroller.cpp"