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

0001 /*
0002     SPDX-FileCopyrightText: 2009 Vladimir Prus <ghost@cs.msu.su>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "mivariable.h"
0008 
0009 #include "debuglog.h"
0010 #include "midebugsession.h"
0011 #include "mi/micommand.h"
0012 #include "stringhelpers.h"
0013 
0014 #include <debugger/interfaces/ivariablecontroller.h>
0015 #include <interfaces/icore.h>
0016 
0017 using namespace KDevelop;
0018 using namespace KDevMI;
0019 using namespace KDevMI::MI;
0020 
0021 bool MIVariable::sessionIsAlive() const
0022 {
0023     if (!m_debugSession)
0024         return false;
0025 
0026     IDebugSession::DebuggerState s = m_debugSession->state();
0027     return s != IDebugSession::NotStartedState 
0028         && s != IDebugSession::EndedState
0029         && !m_debugSession->debuggerStateIsOn(s_shuttingDown);
0030 }
0031 
0032 MIVariable::MIVariable(MIDebugSession *session, TreeModel* model, TreeItem* parent,
0033                        const QString& expression, const QString& display)
0034     : Variable(model, parent, expression, display)
0035     , m_debugSession(session)
0036 {
0037 }
0038 
0039 MIVariable *MIVariable::createChild(const Value& child)
0040 {
0041     if (!m_debugSession) return nullptr;
0042     auto var = static_cast<MIVariable*>(m_debugSession->variableController()->createVariable(model(), this, child[QStringLiteral("exp")].literal()));
0043     var->setTopLevel(false);
0044     var->setVarobj(child[QStringLiteral("name")].literal());
0045     bool hasMore = child[QStringLiteral("numchild")].toInt() != 0 || ( child.hasField(QStringLiteral("dynamic")) && child[QStringLiteral("dynamic")].toInt()!=0 );
0046     var->setHasMoreInitial(hasMore);
0047 
0048     // *this must be parent's child before we can set type and value
0049     appendChild(var);
0050 
0051     var->setType(child[QStringLiteral("type")].literal());
0052     var->setValue(formatValue(child[QStringLiteral("value")].literal()));
0053     var->setChanged(true);
0054     return var;
0055 }
0056 
0057 MIVariable::~MIVariable()
0058 {
0059     if (!m_varobj.isEmpty())
0060     {
0061         // Delete only top-level variable objects.
0062         if (topLevel()) {
0063             if (sessionIsAlive()) {
0064                 m_debugSession->addCommand(VarDelete, QStringLiteral("\"%1\"").arg(m_varobj));
0065             }
0066         }
0067         if (m_debugSession)
0068             m_debugSession->variableMapping().remove(m_varobj);
0069     }
0070 }
0071 
0072 void MIVariable::setVarobj(const QString& v)
0073 {
0074     if (!m_debugSession) {
0075         qCWarning(DEBUGGERCOMMON) << "MIVariable::setVarobj called when its session died";
0076         return;
0077     }
0078     if (!m_varobj.isEmpty()) {
0079         // this should not happen
0080         // but apparently it does when attachMaybe is called a second time before
0081         // the first -var-create call returned
0082         m_debugSession->variableMapping().remove(m_varobj);
0083     }
0084     m_varobj = v;
0085     m_debugSession->variableMapping()[m_varobj] = this;
0086 }
0087 
0088 
0089 static int nextId = 0;
0090 
0091 class CreateVarobjHandler : public MICommandHandler
0092 {
0093 public:
0094     CreateVarobjHandler(MIVariable *variable, QObject *callback, const char *callbackMethod)
0095     : m_variable(variable), m_callback(callback), m_callbackMethod(callbackMethod)
0096     {}
0097 
0098     void handle(const ResultRecord &r) override
0099     {
0100         if (!m_variable) return;
0101         bool hasValue = false;
0102         MIVariable* variable = m_variable.data();
0103         variable->deleteChildren();
0104         variable->setInScope(true);
0105         if (r.reason == QLatin1String("error")) {
0106             variable->setShowError(true);
0107         } else {
0108             variable->setVarobj(r[QStringLiteral("name")].literal());
0109 
0110             bool hasMore = false;
0111             if (r.hasField(QStringLiteral("has_more")) && r[QStringLiteral("has_more")].toInt())
0112                 // GDB swears there are more children. Trust it
0113                 hasMore = true;
0114             else
0115                 // There are no more children in addition to what
0116                 // numchild reports. But, in KDevelop, the variable
0117                 // is not yet expanded, and those numchild are not
0118                 // fetched yet. So, if numchild != 0, hasMore should
0119                 // be true.
0120                 hasMore = r[QStringLiteral("numchild")].toInt() != 0;
0121 
0122             variable->setHasMore(hasMore);
0123 
0124             variable->setType(r[QStringLiteral("type")].literal());
0125             variable->setValue(variable->formatValue(r[QStringLiteral("value")].literal()));
0126             hasValue = !r[QStringLiteral("value")].literal().isEmpty();
0127             if (variable->isExpanded() && r[QStringLiteral("numchild")].toInt()) {
0128                 variable->fetchMoreChildren();
0129             }
0130 
0131             if (variable->format() != KDevelop::Variable::Natural) {
0132                 //TODO doesn't work for children as they are not yet loaded
0133                 variable->formatChanged();
0134             }
0135         }
0136 
0137         if (m_callback && m_callbackMethod) {
0138             QMetaObject::invokeMethod(m_callback, m_callbackMethod, Q_ARG(bool, hasValue));
0139         }
0140     }
0141     bool handlesError() override { return true; }
0142 
0143 private:
0144     QPointer<MIVariable> m_variable;
0145     QObject *m_callback;
0146     const char *m_callbackMethod;
0147 };
0148 
0149 void MIVariable::attachMaybe(QObject *callback, const char *callbackMethod)
0150 {
0151     if (!m_varobj.isEmpty())
0152         return;
0153 
0154     // Try find a current session and attach to it
0155     if (!ICore::self()->debugController()) return; //happens on shutdown
0156     m_debugSession = static_cast<MIDebugSession*>(ICore::self()->debugController()->currentSession());
0157 
0158     if (sessionIsAlive()) {
0159         m_debugSession->addCommand(VarCreate,
0160                                  QStringLiteral("var%1 @ %2").arg(nextId++).arg(enquotedExpression()),
0161                                  new CreateVarobjHandler(this, callback, callbackMethod));
0162     }
0163 }
0164 
0165 void MIVariable::markAsDead()
0166 {
0167     m_varobj.clear();
0168 }
0169 
0170 class FetchMoreChildrenHandler : public MICommandHandler
0171 {
0172 public:
0173     FetchMoreChildrenHandler(MIVariable *variable, MIDebugSession *session)
0174         : m_variable(variable), m_session(session), m_activeCommands(1)
0175     {}
0176 
0177     void handle(const ResultRecord &r) override
0178     {
0179         if (!m_variable) return;
0180         --m_activeCommands;
0181 
0182         MIVariable* variable = m_variable.data();
0183 
0184         if (r.hasField(QStringLiteral("children")))
0185         {
0186             const Value& children = r[QStringLiteral("children")];
0187             for (int i = 0; i < children.size(); ++i) {
0188                 const Value& child = children[i];
0189                 const QString& exp = child[QStringLiteral("exp")].literal();
0190                 if (exp == QLatin1String("public") || exp == QLatin1String("protected") || exp == QLatin1String("private")) {
0191                     ++m_activeCommands;
0192                     m_session->addCommand(VarListChildren,
0193                                           QStringLiteral("--all-values \"%1\"").arg(child[QStringLiteral("name")].literal()),
0194                                           this/*use again as handler*/);
0195                 } else {
0196                     variable->createChild(child);
0197                     // it's automatically appended to variable's children list
0198                 }
0199             }
0200         }
0201 
0202         /* Note that we don't set hasMore to true if there are still active
0203            commands. The reason is that we don't want the user to have
0204            even theoretical ability to click on "..." item and confuse
0205            us.  */
0206         bool hasMore = false;
0207         if (r.hasField(QStringLiteral("has_more")))
0208             hasMore = r[QStringLiteral("has_more")].toInt();
0209 
0210         variable->setHasMore(hasMore);
0211         if (m_activeCommands == 0) {
0212             variable->emitAllChildrenFetched();
0213             delete this;
0214         }
0215     }
0216     bool handlesError() override {
0217         // FIXME: handle error?
0218         return false;
0219     }
0220     bool autoDelete() override {
0221         // we delete ourselves
0222         return false;
0223     }
0224 
0225 private:
0226     QPointer<MIVariable> m_variable;
0227     MIDebugSession *m_session;
0228     int m_activeCommands;
0229 };
0230 
0231 void MIVariable::fetchMoreChildren()
0232 {
0233     int c = childItems.size();
0234     // FIXME: should not even try this if app is not started.
0235     // Probably need to disable open, or something
0236     if (sessionIsAlive()) {
0237         m_debugSession->addCommand(VarListChildren,
0238                                  QStringLiteral("--all-values \"%1\" %2 %3")
0239                                  //   fetch    from ..    to ..
0240                                  .arg(m_varobj).arg(c).arg(c + s_fetchStep),
0241                                  new FetchMoreChildrenHandler(this, m_debugSession));
0242     }
0243 }
0244 
0245 void MIVariable::handleUpdate(const Value& var)
0246 {
0247     if (var.hasField(QStringLiteral("type_changed"))
0248         && var[QStringLiteral("type_changed")].literal() == QLatin1String("true"))
0249     {
0250         deleteChildren();
0251         // FIXME: verify that this check is right.
0252         setHasMore(var[QStringLiteral("new_num_children")].toInt() != 0);
0253         fetchMoreChildren();
0254     }
0255 
0256     if (var.hasField(QStringLiteral("in_scope")) && var[QStringLiteral("in_scope")].literal() == QLatin1String("false"))
0257     {
0258         setInScope(false);
0259     }
0260     else
0261     {
0262         setInScope(true);
0263 
0264         if  (var.hasField(QStringLiteral("new_num_children"))) {
0265             int nc = var[QStringLiteral("new_num_children")].toInt();
0266             Q_ASSERT(nc != -1);
0267             setHasMore(false);
0268             while (childCount() > nc) {
0269                 TreeItem *c = child(childCount()-1);
0270                 removeChild(childCount()-1);
0271                 delete c;
0272             }
0273         }
0274 
0275         if (var.hasField(QStringLiteral("new_children")))
0276         {
0277             const Value& children = var[QStringLiteral("new_children")];
0278             if (m_debugSession) {
0279                 for (int i = 0; i < children.size(); ++i) {
0280                     createChild(children[i]);
0281                     // it's automatically appended to this's children list
0282                 }
0283             }
0284         }
0285 
0286         if (var.hasField(QStringLiteral("type_changed")) && var[QStringLiteral("type_changed")].literal() == QLatin1String("true")) {
0287             setType(var[QStringLiteral("new_type")].literal());
0288         }
0289         setValue(formatValue(var[QStringLiteral("value")].literal()));
0290         setChanged(true);
0291         setHasMore(var.hasField(QStringLiteral("has_more")) && var[QStringLiteral("has_more")].toInt());
0292     }
0293 }
0294 
0295 const QString& MIVariable::varobj() const
0296 {
0297     return m_varobj;
0298 }
0299 
0300 QString MIVariable::enquotedExpression() const
0301 {
0302     return Utils::quoteExpression(expression());
0303 }
0304 
0305 
0306 class SetFormatHandler : public MICommandHandler
0307 {
0308 public:
0309     explicit SetFormatHandler(MIVariable *var)
0310         : m_variable(var)
0311     {}
0312 
0313     void handle(const ResultRecord &r) override
0314     {
0315         if(m_variable && r.hasField(QStringLiteral("value")))
0316             m_variable->setValue(m_variable->formatValue(r[QStringLiteral("value")].literal()));
0317     }
0318 private:
0319     QPointer<MIVariable> m_variable;
0320 };
0321 
0322 void MIVariable::formatChanged()
0323 {
0324     if(childCount())
0325     {
0326         for (TreeItem* item : qAsConst(childItems)) {
0327             Q_ASSERT(dynamic_cast<MIVariable*>(item));
0328             if (auto* var = qobject_cast<MIVariable*>(item)) {
0329                 var->setFormat(format());
0330             }
0331         }
0332     }
0333     else
0334     {
0335         if (sessionIsAlive()) {
0336             m_debugSession->addCommand(VarSetFormat,
0337                                      QStringLiteral(" %1 %2 ").arg(m_varobj, format2str(format())),
0338                                      new SetFormatHandler(this));
0339         }
0340     }
0341 }
0342 
0343 QString MIVariable::formatValue(const QString &rawValue) const
0344 {
0345     return rawValue;
0346 }
0347 
0348 #include "moc_mivariable.cpp"