diff options
-rw-r--r-- | src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro | 6 | ||||
-rw-r--r-- | src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp | 452 | ||||
-rw-r--r-- | src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h | 178 | ||||
-rw-r--r-- | src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.cpp | 1 | ||||
-rw-r--r-- | src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp | 76 | ||||
-rw-r--r-- | src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h | 11 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4context_p.h | 2 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4debugging.cpp | 594 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4debugging_p.h | 88 | ||||
-rw-r--r-- | tests/auto/qml/qv4debugger/qv4debugger.pro | 10 | ||||
-rw-r--r-- | tests/auto/qml/qv4debugger/tst_qv4debugger.cpp | 38 |
11 files changed, 768 insertions, 688 deletions
diff --git a/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro b/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro index dd0c9a174f..d860328dc8 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro +++ b/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro @@ -11,7 +11,8 @@ SOURCES += \ $$PWD/qqmlenginedebugservice.cpp \ $$PWD/qqmlwatcher.cpp \ $$PWD/qv4debugservice.cpp \ - $$PWD/qv4debuggeragent.cpp + $$PWD/qv4debuggeragent.cpp \ + $$PWD/qv4datacollector.cpp HEADERS += \ $$PWD/../shared/qqmlconfigurabledebugservice.h \ @@ -20,7 +21,8 @@ HEADERS += \ $$PWD/qqmlenginedebugservice.h \ $$PWD/qqmlwatcher.h \ $$PWD/qv4debugservice.h \ - $$PWD/qv4debuggeragent.h + $$PWD/qv4debuggeragent.h \ + $$PWD/qv4datacollector.h INCLUDEPATH += $$PWD \ $$PWD/../shared diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp new file mode 100644 index 0000000000..64ee5c3b96 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp @@ -0,0 +1,452 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4datacollector.h" + +#include <private/qv4script_p.h> +#include <private/qv4string_p.h> +#include <private/qv4objectiterator_p.h> +#include <private/qv4identifier_p.h> + +#include <QtCore/qjsonarray.h> + +QT_BEGIN_NAMESPACE + +QV4::Heap::CallContext *QV4DataCollector::findContext(QV4::Heap::ExecutionContext *ctxt, int frame) +{ + if (!ctxt) + return 0; + + QV4::Scope scope(ctxt->engine); + QV4::ScopedContext ctx(scope, ctxt); + while (ctx) { + QV4::CallContext *cCtxt = ctx->asCallContext(); + if (cCtxt && cCtxt->d()->function) { + if (frame < 1) + return cCtxt->d(); + --frame; + } + ctx = ctx->d()->parent; + } + + return 0; +} + +QV4::Heap::CallContext *QV4DataCollector::findScope(QV4::Heap::ExecutionContext *ctxt, int scope) +{ + if (!ctxt) + return 0; + + QV4::Scope s(ctxt->engine); + QV4::ScopedContext ctx(s, ctxt); + for (; scope > 0 && ctx; --scope) + ctx = ctx->d()->outer; + + return (ctx && ctx->d()) ? ctx->asCallContext()->d() : 0; +} + +QVector<QV4::Heap::ExecutionContext::ContextType> QV4DataCollector::getScopeTypes( + QV4::ExecutionEngine *engine, int frame) +{ + QVector<QV4::Heap::ExecutionContext::ContextType> types; + + QV4::Scope scope(engine); + QV4::Scoped<QV4::CallContext> sctxt(scope, findContext(engine->currentContext(), frame)); + if (!sctxt || sctxt->d()->type < QV4::Heap::ExecutionContext::Type_QmlContext) + return types; + + QV4::ScopedContext it(scope, sctxt->d()); + for (; it; it = it->d()->outer) + types.append(it->d()->type); + + return types; +} + + +QV4DataCollector::QV4DataCollector(QV4::ExecutionEngine *engine) + : m_engine(engine), m_collectedRefs(Q_NULLPTR) +{ + values.set(engine, engine->newArrayObject()); +} + +QV4DataCollector::~QV4DataCollector() +{ +} + +void QV4DataCollector::collect(const QV4::ScopedValue &value) +{ + if (m_collectedRefs) + m_collectedRefs->append(addRef(value)); +} + +QJsonObject QV4DataCollector::lookupRef(Ref ref) +{ + QJsonObject dict; + if (lookupSpecialRef(ref, &dict)) + return dict; + + dict.insert(QStringLiteral("handle"), qint64(ref)); + + QV4::Scope scope(engine()); + QV4::ScopedValue value(scope, getValue(ref)); + switch (value->type()) { + case QV4::Value::Empty_Type: + Q_ASSERT(!"empty Value encountered"); + break; + case QV4::Value::Undefined_Type: + dict.insert(QStringLiteral("type"), QStringLiteral("undefined")); + break; + case QV4::Value::Null_Type: + dict.insert(QStringLiteral("type"), QStringLiteral("null")); + break; + case QV4::Value::Boolean_Type: + dict.insert(QStringLiteral("type"), QStringLiteral("boolean")); + dict.insert(QStringLiteral("value"), value->booleanValue() ? QStringLiteral("true") + : QStringLiteral("false")); + break; + case QV4::Value::Managed_Type: + if (QV4::String *s = value->as<QV4::String>()) { + dict.insert(QStringLiteral("type"), QStringLiteral("string")); + dict.insert(QStringLiteral("value"), s->toQString()); + } else if (QV4::Object *o = value->as<QV4::Object>()) { + dict.insert(QStringLiteral("type"), QStringLiteral("object")); + dict.insert(QStringLiteral("properties"), collectProperties(o)); + } else { + Q_UNREACHABLE(); + } + break; + case QV4::Value::Integer_Type: + dict.insert(QStringLiteral("type"), QStringLiteral("number")); + dict.insert(QStringLiteral("value"), value->integerValue()); + break; + default: // double + dict.insert(QStringLiteral("type"), QStringLiteral("number")); + dict.insert(QStringLiteral("value"), value->doubleValue()); + break; + } + + return dict; +} + +QV4DataCollector::Ref QV4DataCollector::addFunctionRef(const QString &functionName) +{ + Ref ref = addRef(QV4::Primitive::emptyValue(), false); + + QJsonObject dict; + dict.insert(QStringLiteral("handle"), qint64(ref)); + dict.insert(QStringLiteral("type"), QStringLiteral("function")); + dict.insert(QStringLiteral("className"), QStringLiteral("Function")); + dict.insert(QStringLiteral("name"), functionName); + specialRefs.insert(ref, dict); + + return ref; +} + +QV4DataCollector::Ref QV4DataCollector::addScriptRef(const QString &scriptName) +{ + Ref ref = addRef(QV4::Primitive::emptyValue(), false); + + QJsonObject dict; + dict.insert(QStringLiteral("handle"), qint64(ref)); + dict.insert(QStringLiteral("type"), QStringLiteral("script")); + dict.insert(QStringLiteral("name"), scriptName); + specialRefs.insert(ref, dict); + + return ref; +} + +void QV4DataCollector::collectScope(QJsonObject *dict, QV4::Debugging::Debugger *debugger, + int frameNr, int scopeNr) +{ + QStringList names; + + Refs refs; + if (debugger->state() == QV4::Debugging::Debugger::Paused) { + RefHolder holder(this, &refs); + ArgumentCollectJob argumentsJob(m_engine, this, &names, frameNr, scopeNr); + debugger->runInEngine(&argumentsJob); + LocalCollectJob localsJob(m_engine, this, &names, frameNr, scopeNr); + debugger->runInEngine(&localsJob); + } + + QV4::Scope scope(engine()); + QV4::ScopedObject scopeObject(scope, engine()->newObject()); + + Q_ASSERT(names.size() == refs.size()); + for (int i = 0, ei = refs.size(); i != ei; ++i) + scopeObject->put(engine(), names.at(i), + QV4::Value::fromReturnedValue(getValue(refs.at(i)))); + + Ref scopeObjectRef = addRef(scopeObject); + dict->insert(QStringLiteral("ref"), qint64(scopeObjectRef)); + if (m_collectedRefs) + m_collectedRefs->append(scopeObjectRef); +} + +QV4DataCollector::Ref QV4DataCollector::addRef(QV4::Value value, bool deduplicate) +{ + class ExceptionStateSaver + { + quint32 *hasExceptionLoc; + quint32 hadException; + + public: + ExceptionStateSaver(QV4::ExecutionEngine *engine) + : hasExceptionLoc(&engine->hasException) + , hadException(false) + { std::swap(*hasExceptionLoc, hadException); } + + ~ExceptionStateSaver() + { std::swap(*hasExceptionLoc, hadException); } + }; + + // if we wouldn't do this, the putIndexed won't work. + ExceptionStateSaver resetExceptionState(engine()); + QV4::Scope scope(engine()); + QV4::ScopedObject array(scope, values.value()); + if (deduplicate) { + for (Ref i = 0; i < array->getLength(); ++i) { + if (array->getIndexed(i) == value.rawValue()) + return i; + } + } + Ref ref = array->getLength(); + array->putIndexed(ref, value); + Q_ASSERT(array->getLength() - 1 == ref); + return ref; +} + +QV4::ReturnedValue QV4DataCollector::getValue(Ref ref) +{ + QV4::Scope scope(engine()); + QV4::ScopedObject array(scope, values.value()); + Q_ASSERT(ref < array->getLength()); + return array->getIndexed(ref, Q_NULLPTR); +} + +bool QV4DataCollector::lookupSpecialRef(Ref ref, QJsonObject *dict) +{ + SpecialRefs::const_iterator it = specialRefs.find(ref); + if (it == specialRefs.end()) + return false; + + *dict = it.value(); + return true; +} + +QJsonArray QV4DataCollector::collectProperties(QV4::Object *object) +{ + QJsonArray res; + + QV4::Scope scope(engine()); + QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly); + QV4::ScopedValue name(scope); + QV4::ScopedValue value(scope); + while (true) { + QV4::Value v; + name = it.nextPropertyNameAsString(&v); + if (name->isNull()) + break; + QString key = name->toQStringNoThrow(); + value = v; + res.append(collectAsJson(key, value)); + } + + return res; +} + +QJsonObject QV4DataCollector::collectAsJson(const QString &name, const QV4::ScopedValue &value) +{ + QJsonObject dict; + if (!name.isNull()) + dict.insert(QStringLiteral("name"), name); + Ref ref = addRef(value); + dict.insert(QStringLiteral("ref"), qint64(ref)); + if (m_collectedRefs) + m_collectedRefs->append(ref); + + // TODO: enable this when creator can handle it. + if (false) { + if (value->isManaged() && !value->isString()) { + QV4::Scope scope(engine()); + QV4::ScopedObject obj(scope, value->as<QV4::Object>()); + dict.insert(QStringLiteral("propertycount"), qint64(obj->getLength())); + } + } + + return dict; +} + +ExpressionEvalJob::ExpressionEvalJob(QV4::ExecutionEngine *engine, int frameNr, + const QString &expression, + QV4DataCollector *collector) + : JavaScriptJob(engine, frameNr, expression) + , collector(collector) +{ +} + +void ExpressionEvalJob::handleResult(QV4::ScopedValue &result) +{ + collector->collect(result); +} + +GatherSourcesJob::GatherSourcesJob(QV4::ExecutionEngine *engine, int seq) + : engine(engine) + , seq(seq) +{} + +void GatherSourcesJob::run() +{ + QStringList sources; + + foreach (QV4::CompiledData::CompilationUnit *unit, engine->compilationUnits) { + QString fileName = unit->fileName(); + if (!fileName.isEmpty()) + sources.append(fileName); + } + + QV4::Debugging::Debugger *debugger = engine->debugger; + emit debugger->sourcesCollected(debugger, sources, seq); +} + +ArgumentCollectJob::ArgumentCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector, + QStringList *names, int frameNr, int scopeNr) + : engine(engine) + , collector(collector) + , names(names) + , frameNr(frameNr) + , scopeNr(scopeNr) +{} + +void ArgumentCollectJob::run() +{ + if (frameNr < 0) + return; + + QV4::Scope scope(engine); + QV4::Scoped<QV4::CallContext> ctxt( + scope, QV4DataCollector::findScope( + QV4DataCollector::findContext(engine->currentContext(), frameNr), scopeNr)); + if (!ctxt) + return; + + QV4::ScopedValue v(scope); + int nFormals = ctxt->formalCount(); + for (unsigned i = 0, ei = nFormals; i != ei; ++i) { + QString qName; + if (QV4::Identifier *name = ctxt->formals()[nFormals - i - 1]) + qName = name->string; + names->append(qName); + v = ctxt->argument(i); + collector->collect(v); + } +} + +LocalCollectJob::LocalCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector, + QStringList *names, int frameNr, int scopeNr) + : engine(engine) + , collector(collector) + , names(names) + , frameNr(frameNr) + , scopeNr(scopeNr) +{} + +void LocalCollectJob::run() +{ + if (frameNr < 0) + return; + + QV4::Scope scope(engine); + QV4::Scoped<QV4::CallContext> ctxt( + scope, QV4DataCollector::findScope( + QV4DataCollector::findContext(engine->currentContext(), frameNr), scopeNr)); + if (!ctxt) + return; + + QV4::ScopedValue v(scope); + for (unsigned i = 0, ei = ctxt->variableCount(); i != ei; ++i) { + QString qName; + if (QV4::Identifier *name = ctxt->variables()[i]) + qName = name->string; + names->append(qName); + v = ctxt->d()->locals[i]; + collector->collect(v); + } +} + +ThisCollectJob::ThisCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector, + int frameNr, bool *foundThis) + : engine(engine) + , collector(collector) + , frameNr(frameNr) + , foundThis(foundThis) +{} + +void ThisCollectJob::run() +{ + *foundThis = myRun(); +} + +bool ThisCollectJob::myRun() +{ + QV4::Scope scope(engine); + QV4::ScopedContext ctxt(scope, QV4DataCollector::findContext(engine->currentContext(), + frameNr)); + while (ctxt) { + if (QV4::CallContext *cCtxt = ctxt->asCallContext()) + if (cCtxt->d()->activation) + break; + ctxt = ctxt->d()->outer; + } + + if (!ctxt) + return false; + + QV4::ScopedValue o(scope, ctxt->asCallContext()->d()->activation); + collector->collect(o); + return true; +} + +ExceptionCollectJob::ExceptionCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector) + : engine(engine) + , collector(collector) +{} + +void ExceptionCollectJob::run() +{ + QV4::Scope scope(engine); + QV4::ScopedValue v(scope, *engine->exceptionValue); + collector->collect(v); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h new file mode 100644 index 0000000000..c91b77cb93 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4DATACOLLECTOR_H +#define QV4DATACOLLECTOR_H + +#include <private/qv4engine_p.h> +#include <private/qv4debugging_p.h> + +QT_BEGIN_NAMESPACE + +class QV4DataCollector +{ +public: + typedef uint Ref; + typedef QVector<uint> Refs; + + static QV4::Heap::CallContext *findContext(QV4::Heap::ExecutionContext *ctxt, int frame); + static QV4::Heap::CallContext *findScope(QV4::Heap::ExecutionContext *ctxt, int scope); + static QVector<QV4::Heap::ExecutionContext::ContextType> getScopeTypes( + QV4::ExecutionEngine *engine, int frame); + + QV4DataCollector(QV4::ExecutionEngine *engine); + ~QV4DataCollector(); + + void collect(const QV4::ScopedValue &value); + + QJsonObject lookupRef(Ref ref); + + Ref addFunctionRef(const QString &functionName); + Ref addScriptRef(const QString &scriptName); + + void collectScope(QJsonObject *dict, QV4::Debugging::Debugger *debugger, int frameNr, + int scopeNr); + + QV4::ExecutionEngine *engine() const { return m_engine; } + +private: + friend class RefHolder; + + Ref addRef(QV4::Value value, bool deduplicate = true); + QV4::ReturnedValue getValue(Ref ref); + bool lookupSpecialRef(Ref ref, QJsonObject *dict); + + QJsonArray collectProperties(QV4::Object *object); + QJsonObject collectAsJson(const QString &name, const QV4::ScopedValue &value); + void collectArgumentsInContext(); + + QV4::ExecutionEngine *m_engine; + Refs *m_collectedRefs; + QV4::PersistentValue values; + typedef QHash<Ref, QJsonObject> SpecialRefs; + SpecialRefs specialRefs; +}; + +class RefHolder { +public: + RefHolder(QV4DataCollector *collector, QV4DataCollector::Refs *target) : + m_collector(collector), m_previousRefs(collector->m_collectedRefs) + { + m_collector->m_collectedRefs = target; + } + + ~RefHolder() + { + std::swap(m_collector->m_collectedRefs, m_previousRefs); + } + +private: + QV4DataCollector *m_collector; + QV4DataCollector::Refs *m_previousRefs; +}; + +class ExpressionEvalJob: public QV4::Debugging::Debugger::JavaScriptJob +{ + QV4DataCollector *collector; + +public: + ExpressionEvalJob(QV4::ExecutionEngine *engine, int frameNr, const QString &expression, + QV4DataCollector *collector); + virtual void handleResult(QV4::ScopedValue &result); +}; + +class GatherSourcesJob: public QV4::Debugging::Debugger::Job +{ + QV4::ExecutionEngine *engine; + const int seq; + +public: + GatherSourcesJob(QV4::ExecutionEngine *engine, int seq); + void run(); +}; + +class ArgumentCollectJob: public QV4::Debugging::Debugger::Job +{ + QV4::ExecutionEngine *engine; + QV4DataCollector *collector; + QStringList *names; + int frameNr; + int scopeNr; + +public: + ArgumentCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector, + QStringList *names, int frameNr, int scopeNr); + void run(); +}; + +class LocalCollectJob: public QV4::Debugging::Debugger::Job +{ + QV4::ExecutionEngine *engine; + QV4DataCollector *collector; + QStringList *names; + int frameNr; + int scopeNr; + +public: + LocalCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector, QStringList *names, + int frameNr, int scopeNr); + void run(); +}; + +class ThisCollectJob: public QV4::Debugging::Debugger::Job +{ + QV4::ExecutionEngine *engine; + QV4DataCollector *collector; + int frameNr; + bool *foundThis; + +public: + ThisCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector, int frameNr, + bool *foundThis); + void run(); + bool myRun(); +}; + +class ExceptionCollectJob: public QV4::Debugging::Debugger::Job +{ + QV4::ExecutionEngine *engine; + QV4DataCollector *collector; + +public: + ExceptionCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector); + void run(); +}; + +QT_END_NAMESPACE + +#endif // QV4DATACOLLECTOR_H diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.cpp index 15421f4f5d..7f22b16e45 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.cpp @@ -33,6 +33,7 @@ #include "qv4debuggeragent.h" #include "qv4debugservice.h" +#include "qv4datacollector.h" #include <QtCore/qjsonobject.h> #include <QtCore/qjsonarray.h> diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp index 21bfe97808..6b68f9518e 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp @@ -506,7 +506,9 @@ public: } // do it: - debugService->debuggerAgent.firstDebugger()->gatherSources(requestSequenceNr()); + QV4::Debugging::Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + GatherSourcesJob job(debugger->engine(), requestSequenceNr()); + debugger->runInEngine(&job); // response will be send by } @@ -555,10 +557,13 @@ public: QV4::Debugging::Debugger *debugger = debugService->debuggerAgent.firstDebugger(); Q_ASSERT(debugger->state() == QV4::Debugging::Debugger::Paused); - QV4::Debugging::DataCollector *collector = debugService->collector(); - QV4::Debugging::DataCollector::Refs refs; - QV4::Debugging::RefHolder holder(collector, &refs); - debugger->evaluateExpression(frame, expression, collector); + QV4DataCollector *collector = debugService->collector(); + QV4DataCollector::Refs refs; + RefHolder holder(collector, &refs); + Q_ASSERT(debugger->state() == QV4::Debugging::Debugger::Paused); + + ExpressionEvalJob job(debugger->engine(), frame, expression, collector); + debugger->runInEngine(&job); Q_ASSERT(refs.size() == 1); @@ -762,13 +767,13 @@ void QV4DebugServiceImpl::send(QJsonObject v8Payload) void QV4DebugServiceImpl::clearHandles(QV4::ExecutionEngine *engine) { - theCollector.reset(new QV4::Debugging::DataCollector(engine)); + theCollector.reset(new QV4DataCollector(engine)); } QJsonObject QV4DebugServiceImpl::buildFrame(const QV4::StackFrame &stackFrame, int frameNr, QV4::Debugging::Debugger *debugger) { - QV4::Debugging::DataCollector::Ref ref; + QV4DataCollector::Ref ref; QJsonObject frame; frame[QLatin1String("index")] = frameNr; @@ -783,24 +788,28 @@ QJsonObject QV4DebugServiceImpl::buildFrame(const QV4::StackFrame &stackFrame, i if (stackFrame.column >= 0) frame[QLatin1String("column")] = stackFrame.column; - { - QV4::Debugging::RefHolder holder(theCollector.data(), &collectedRefs); - if (debugger->collectThisInContext(theCollector.data(), frameNr)) - frame[QLatin1String("receiver")] = toRef(collectedRefs.last()); - } - QJsonArray scopes; - // Only type and index are used by Qt Creator, so we keep it easy: - QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes = debugger->getScopeTypes(frameNr); - for (int i = 0, ei = scopeTypes.count(); i != ei; ++i) { - int type = encodeScopeType(scopeTypes[i]); - if (type == -1) - continue; + if (debugger->state() == QV4::Debugging::Debugger::Paused) { + RefHolder holder(theCollector.data(), &collectedRefs); + bool foundThis = false; + ThisCollectJob job(debugger->engine(), theCollector.data(), frameNr, &foundThis); + debugger->runInEngine(&job); + if (foundThis) + frame[QLatin1String("receiver")] = toRef(collectedRefs.last()); - QJsonObject scope; - scope[QLatin1String("index")] = i; - scope[QLatin1String("type")] = type; - scopes.push_back(scope); + // Only type and index are used by Qt Creator, so we keep it easy: + QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes = + QV4DataCollector::getScopeTypes(debugger->engine(), frameNr); + for (int i = 0, ei = scopeTypes.count(); i != ei; ++i) { + int type = encodeScopeType(scopeTypes[i]); + if (type == -1) + continue; + + QJsonObject scope; + scope[QLatin1String("index")] = i; + scope[QLatin1String("type")] = type; + scopes.push_back(scope); + } } frame[QLatin1String("scopes")] = scopes; @@ -835,11 +844,16 @@ QJsonObject QV4DebugServiceImpl::buildScope(int frameNr, int scopeNr, QJsonObject scope; QJsonObject object; - QV4::Debugging::RefHolder holder(theCollector.data(), &collectedRefs); + RefHolder holder(theCollector.data(), &collectedRefs); theCollector->collectScope(&object, debugger, frameNr, scopeNr); - QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes = debugger->getScopeTypes(frameNr); - scope[QLatin1String("type")] = encodeScopeType(scopeTypes[scopeNr]); + if (debugger->state() == QV4::Debugging::Debugger::Paused) { + QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes = + QV4DataCollector::getScopeTypes(debugger->engine(), frameNr); + scope[QLatin1String("type")] = encodeScopeType(scopeTypes[scopeNr]); + } else { + scope[QLatin1String("type")] = -1; + } scope[QLatin1String("index")] = scopeNr; scope[QLatin1String("frameIndex")] = frameNr; scope[QLatin1String("object")] = object; @@ -847,9 +861,9 @@ QJsonObject QV4DebugServiceImpl::buildScope(int frameNr, int scopeNr, return scope; } -QJsonValue QV4DebugServiceImpl::lookup(QV4::Debugging::DataCollector::Ref refId) +QJsonValue QV4DebugServiceImpl::lookup(QV4DataCollector::Ref refId) { - QV4::Debugging::RefHolder holder(theCollector.data(), &collectedRefs); + RefHolder holder(theCollector.data(), &collectedRefs); return theCollector->lookupRef(refId); } @@ -858,7 +872,7 @@ QJsonArray QV4DebugServiceImpl::buildRefs() QJsonArray refs; std::sort(collectedRefs.begin(), collectedRefs.end()); for (int i = 0, ei = collectedRefs.size(); i != ei; ++i) { - QV4::Debugging::DataCollector::Ref ref = collectedRefs.at(i); + QV4DataCollector::Ref ref = collectedRefs.at(i); if (i > 0 && ref == collectedRefs.at(i - 1)) continue; refs.append(lookup(ref)); @@ -868,14 +882,14 @@ QJsonArray QV4DebugServiceImpl::buildRefs() return refs; } -QJsonValue QV4DebugServiceImpl::toRef(QV4::Debugging::DataCollector::Ref ref) +QJsonValue QV4DebugServiceImpl::toRef(QV4DataCollector::Ref ref) { QJsonObject dict; dict.insert(QStringLiteral("ref"), qint64(ref)); return dict; } -QV4::Debugging::DataCollector *QV4DebugServiceImpl::collector() const +QV4DataCollector *QV4DebugServiceImpl::collector() const { return theCollector.data(); } diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h index 6e5113600d..c80ad78cc8 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h @@ -47,6 +47,7 @@ #include "qqmlconfigurabledebugservice.h" #include "qv4debuggeragent.h" +#include "qv4datacollector.h" #include <private/qqmldebugserviceinterfaces_p.h> #include <private/qv4debugging_p.h> @@ -77,8 +78,8 @@ public: QJsonObject buildScope(int frameNr, int scopeNr, QV4::Debugging::Debugger *debugger); QJsonArray buildRefs(); - QJsonValue lookup(QV4::Debugging::DataCollector::Ref refId); - QJsonValue toRef(QV4::Debugging::DataCollector::Ref ref); + QJsonValue lookup(QV4DataCollector::Ref refId); + QJsonValue toRef(QV4DataCollector::Ref ref); QJsonObject buildFrame(const QV4::StackFrame &stackFrame, int frameNr, QV4::Debugging::Debugger *debugger); @@ -87,7 +88,7 @@ public: void clearHandles(QV4::ExecutionEngine *engine); - QV4::Debugging::DataCollector *collector() const; + QV4DataCollector *collector() const; QV4DebuggerAgent debuggerAgent; protected: @@ -109,9 +110,9 @@ private: static int debuggerIndex; static int sequence; const int version; - QV4::Debugging::DataCollector::Refs collectedRefs; + QV4DataCollector::Refs collectedRefs; - QScopedPointer<QV4::Debugging::DataCollector> theCollector; + QScopedPointer<QV4DataCollector> theCollector; int theSelectedFrame; void addHandler(V8CommandHandler* handler); diff --git a/src/qml/jsruntime/qv4context_p.h b/src/qml/jsruntime/qv4context_p.h index c698a6c9c3..1e051ed850 100644 --- a/src/qml/jsruntime/qv4context_p.h +++ b/src/qml/jsruntime/qv4context_p.h @@ -181,7 +181,7 @@ struct Q_QML_EXPORT ExecutionContext : public Managed } }; -struct CallContext : public ExecutionContext +struct Q_QML_EXPORT CallContext : public ExecutionContext { V4_MANAGED(CallContext, ExecutionContext) diff --git a/src/qml/jsruntime/qv4debugging.cpp b/src/qml/jsruntime/qv4debugging.cpp index 6f3e48352e..ceeef80b9f 100644 --- a/src/qml/jsruntime/qv4debugging.cpp +++ b/src/qml/jsruntime/qv4debugging.cpp @@ -54,60 +54,49 @@ QT_BEGIN_NAMESPACE using namespace QV4; using namespace QV4::Debugging; -namespace { -class JavaScriptJob: public Debugger::Job -{ - QV4::ExecutionEngine *engine; - int frameNr; - const QString &script; - -public: - JavaScriptJob(QV4::ExecutionEngine *engine, int frameNr, const QString &script) - : engine(engine) - , frameNr(frameNr) - , script(script) - {} +Debugger::JavaScriptJob::JavaScriptJob(QV4::ExecutionEngine *engine, int frameNr, + const QString &script) + : engine(engine) + , frameNr(frameNr) + , script(script) +{} - void run() - { - Scope scope(engine); +void Debugger::JavaScriptJob::run() +{ + Scope scope(engine); - ExecutionContextSaver saver(scope, engine->currentContext()); + ExecutionContextSaver saver(scope, engine->currentContext()); - if (frameNr > 0) { - Value *savedContexts = scope.alloc(frameNr); - for (int i = 0; i < frameNr; ++i) { - savedContexts[i] = engine->currentContext(); - engine->popContext(); - } + if (frameNr > 0) { + Value *savedContexts = scope.alloc(frameNr); + for (int i = 0; i < frameNr; ++i) { + savedContexts[i] = engine->currentContext(); + engine->popContext(); } - - ScopedContext ctx(scope, engine->currentContext()); - QV4::Script script(ctx, this->script); - script.strictMode = ctx->d()->strictMode; - // In order for property lookups in QML to work, we need to disable fast v4 lookups. That - // is a side-effect of inheritContext. - script.inheritContext = true; - script.parse(); - QV4::ScopedValue result(scope); - if (!scope.engine->hasException) - result = script.run(); - if (scope.engine->hasException) - result = scope.engine->catchException(); - handleResult(result); } -protected: - virtual void handleResult(QV4::ScopedValue &result) = 0; -}; + ScopedContext ctx(scope, engine->currentContext()); + QV4::Script script(ctx, this->script); + script.strictMode = ctx->d()->strictMode; + // In order for property lookups in QML to work, we need to disable fast v4 lookups. That + // is a side-effect of inheritContext. + script.inheritContext = true; + script.parse(); + QV4::ScopedValue result(scope); + if (!scope.engine->hasException) + result = script.run(); + if (scope.engine->hasException) + result = scope.engine->catchException(); + handleResult(result); +} -class EvalJob: public JavaScriptJob +class EvalJob: public Debugger::JavaScriptJob { bool result; public: EvalJob(QV4::ExecutionEngine *engine, const QString &script) - : JavaScriptJob(engine, /*frameNr*/-1, script) + : Debugger::JavaScriptJob(engine, /*frameNr*/-1, script) , result(false) {} @@ -122,265 +111,6 @@ public: } }; -class ExpressionEvalJob: public JavaScriptJob -{ - QV4::Debugging::DataCollector *collector; - -public: - ExpressionEvalJob(ExecutionEngine *engine, int frameNr, const QString &expression, - QV4::Debugging::DataCollector *collector) - : JavaScriptJob(engine, frameNr, expression) - , collector(collector) - { - } - - virtual void handleResult(QV4::ScopedValue &result) - { - collector->collect(result); - } -}; - -class GatherSourcesJob: public Debugger::Job -{ - QV4::ExecutionEngine *engine; - const int seq; - -public: - GatherSourcesJob(QV4::ExecutionEngine *engine, int seq) - : engine(engine) - , seq(seq) - {} - - ~GatherSourcesJob() {} - - void run() - { - QStringList sources; - - foreach (QV4::CompiledData::CompilationUnit *unit, engine->compilationUnits) { - QString fileName = unit->fileName(); - if (!fileName.isEmpty()) - sources.append(fileName); - } - - Debugger *debugger = engine->debugger; - emit debugger->sourcesCollected(debugger, sources, seq); - } -}; -} - - -DataCollector::DataCollector(QV4::ExecutionEngine *engine) - : m_engine(engine), m_collectedRefs(Q_NULLPTR) -{ - values.set(engine, engine->newArrayObject()); -} - -DataCollector::~DataCollector() -{ -} - -void DataCollector::collect(const ScopedValue &value) -{ - if (m_collectedRefs) - m_collectedRefs->append(addRef(value)); -} - -QJsonObject DataCollector::lookupRef(Ref ref) -{ - QJsonObject dict; - if (lookupSpecialRef(ref, &dict)) - return dict; - - dict.insert(QStringLiteral("handle"), qint64(ref)); - - QV4::Scope scope(engine()); - QV4::ScopedValue value(scope, getValue(ref)); - switch (value->type()) { - case QV4::Value::Empty_Type: - Q_ASSERT(!"empty Value encountered"); - break; - case QV4::Value::Undefined_Type: - dict.insert(QStringLiteral("type"), QStringLiteral("undefined")); - break; - case QV4::Value::Null_Type: - dict.insert(QStringLiteral("type"), QStringLiteral("null")); - break; - case QV4::Value::Boolean_Type: - dict.insert(QStringLiteral("type"), QStringLiteral("boolean")); - dict.insert(QStringLiteral("value"), value->booleanValue() ? QStringLiteral("true") - : QStringLiteral("false")); - break; - case QV4::Value::Managed_Type: - if (QV4::String *s = value->as<QV4::String>()) { - dict.insert(QStringLiteral("type"), QStringLiteral("string")); - dict.insert(QStringLiteral("value"), s->toQString()); - } else if (QV4::Object *o = value->as<QV4::Object>()) { - dict.insert(QStringLiteral("type"), QStringLiteral("object")); - dict.insert(QStringLiteral("properties"), collectProperties(o)); - } else { - Q_UNREACHABLE(); - } - break; - case QV4::Value::Integer_Type: - dict.insert(QStringLiteral("type"), QStringLiteral("number")); - dict.insert(QStringLiteral("value"), value->integerValue()); - break; - default: // double - dict.insert(QStringLiteral("type"), QStringLiteral("number")); - dict.insert(QStringLiteral("value"), value->doubleValue()); - break; - } - - return dict; -} - -DataCollector::Ref DataCollector::addFunctionRef(const QString &functionName) -{ - Ref ref = addRef(QV4::Primitive::emptyValue(), false); - - QJsonObject dict; - dict.insert(QStringLiteral("handle"), qint64(ref)); - dict.insert(QStringLiteral("type"), QStringLiteral("function")); - dict.insert(QStringLiteral("className"), QStringLiteral("Function")); - dict.insert(QStringLiteral("name"), functionName); - specialRefs.insert(ref, dict); - - return ref; -} - -DataCollector::Ref DataCollector::addScriptRef(const QString &scriptName) -{ - Ref ref = addRef(QV4::Primitive::emptyValue(), false); - - QJsonObject dict; - dict.insert(QStringLiteral("handle"), qint64(ref)); - dict.insert(QStringLiteral("type"), QStringLiteral("script")); - dict.insert(QStringLiteral("name"), scriptName); - specialRefs.insert(ref, dict); - - return ref; -} - -void DataCollector::collectScope(QJsonObject *dict, Debugger *debugger, int frameNr, int scopeNr) -{ - QStringList names; - - Refs refs; - { - RefHolder holder(this, &refs); - debugger->collectArgumentsInContext(this, &names, frameNr, scopeNr); - debugger->collectLocalsInContext(this, &names, frameNr, scopeNr); - } - - QV4::Scope scope(engine()); - QV4::ScopedObject scopeObject(scope, engine()->newObject()); - - Q_ASSERT(names.size() == refs.size()); - for (int i = 0, ei = refs.size(); i != ei; ++i) - scopeObject->put(engine(), names.at(i), - QV4::Value::fromReturnedValue(getValue(refs.at(i)))); - - Ref scopeObjectRef = addRef(scopeObject); - dict->insert(QStringLiteral("ref"), qint64(scopeObjectRef)); - if (m_collectedRefs) - m_collectedRefs->append(scopeObjectRef); -} - -DataCollector::Ref DataCollector::addRef(Value value, bool deduplicate) -{ - class ExceptionStateSaver - { - quint32 *hasExceptionLoc; - quint32 hadException; - - public: - ExceptionStateSaver(QV4::ExecutionEngine *engine) - : hasExceptionLoc(&engine->hasException) - , hadException(false) - { std::swap(*hasExceptionLoc, hadException); } - - ~ExceptionStateSaver() - { std::swap(*hasExceptionLoc, hadException); } - }; - - // if we wouldn't do this, the putIndexed won't work. - ExceptionStateSaver resetExceptionState(engine()); - QV4::Scope scope(engine()); - QV4::ScopedObject array(scope, values.value()); - if (deduplicate) { - for (Ref i = 0; i < array->getLength(); ++i) { - if (array->getIndexed(i) == value.rawValue()) - return i; - } - } - Ref ref = array->getLength(); - array->putIndexed(ref, value); - Q_ASSERT(array->getLength() - 1 == ref); - return ref; -} - -ReturnedValue DataCollector::getValue(Ref ref) -{ - QV4::Scope scope(engine()); - QV4::ScopedObject array(scope, values.value()); - Q_ASSERT(ref < array->getLength()); - return array->getIndexed(ref, Q_NULLPTR); -} - -bool DataCollector::lookupSpecialRef(Ref ref, QJsonObject *dict) -{ - SpecialRefs::const_iterator it = specialRefs.find(ref); - if (it == specialRefs.end()) - return false; - - *dict = it.value(); - return true; -} - -QJsonArray DataCollector::collectProperties(Object *object) -{ - QJsonArray res; - - QV4::Scope scope(engine()); - QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly); - QV4::ScopedValue name(scope); - QV4::ScopedValue value(scope); - while (true) { - QV4::Value v; - name = it.nextPropertyNameAsString(&v); - if (name->isNull()) - break; - QString key = name->toQStringNoThrow(); - value = v; - res.append(collectAsJson(key, value)); - } - - return res; -} - -QJsonObject DataCollector::collectAsJson(const QString &name, const ScopedValue &value) -{ - QJsonObject dict; - if (!name.isNull()) - dict.insert(QStringLiteral("name"), name); - Ref ref = addRef(value); - dict.insert(QStringLiteral("ref"), qint64(ref)); - if (m_collectedRefs) - m_collectedRefs->append(ref); - - // TODO: enable this when creator can handle it. - if (false) { - if (value->isManaged() && !value->isString()) { - QV4::Scope scope(engine()); - QV4::ScopedObject obj(scope, value->as<QV4::Object>()); - dict.insert(QStringLiteral("propertycount"), qint64(obj->getLength())); - } - } - - return dict; -} - Debugger::Debugger(QV4::ExecutionEngine *engine) : m_engine(engine) , m_state(Running) @@ -396,18 +126,6 @@ Debugger::Debugger(QV4::ExecutionEngine *engine) qMetaTypeId<PauseReason>(); } -void Debugger::gatherSources(int requestSequenceNr) -{ - QMutexLocker locker(&m_lock); - - m_gatherSources = new GatherSourcesJob(m_engine, requestSequenceNr); - if (m_state == Paused) { - runInEngine_havingLock(m_gatherSources); - delete m_gatherSources; - m_gatherSources = 0; - } -} - void Debugger::pause() { QMutexLocker locker(&m_lock); @@ -465,256 +183,6 @@ QVector<StackFrame> Debugger::stackTrace(int frameLimit) const return m_engine->stackTrace(frameLimit); } -static inline Heap::CallContext *findContext(Heap::ExecutionContext *ctxt, int frame) -{ - if (!ctxt) - return 0; - - Scope scope(ctxt->engine); - ScopedContext ctx(scope, ctxt); - while (ctx) { - CallContext *cCtxt = ctx->asCallContext(); - if (cCtxt && cCtxt->d()->function) { - if (frame < 1) - return cCtxt->d(); - --frame; - } - ctx = ctx->d()->parent; - } - - return 0; -} - -static inline Heap::CallContext *findScope(Heap::ExecutionContext *ctxt, int scope) -{ - if (!ctxt) - return 0; - - Scope s(ctxt->engine); - ScopedContext ctx(s, ctxt); - for (; scope > 0 && ctx; --scope) - ctx = ctx->d()->outer; - - return (ctx && ctx->d()) ? ctx->asCallContext()->d() : 0; -} - -void Debugger::collectArgumentsInContext(DataCollector *collector, QStringList *names, int frameNr, - int scopeNr) -{ - if (state() != Paused) - return; - - class ArgumentCollectJob: public Job - { - QV4::ExecutionEngine *engine; - QV4::Debugging::DataCollector *collector; - QStringList *names; - int frameNr; - int scopeNr; - - public: - ArgumentCollectJob(QV4::ExecutionEngine *engine, DataCollector *collector, - QStringList *names, int frameNr, int scopeNr) - : engine(engine) - , collector(collector) - , names(names) - , frameNr(frameNr) - , scopeNr(scopeNr) - {} - - ~ArgumentCollectJob() {} - - void run() - { - if (frameNr < 0) - return; - - Scope scope(engine); - Scoped<CallContext> ctxt(scope, findScope(findContext(engine->currentContext(), frameNr), scopeNr)); - if (!ctxt) - return; - - ScopedValue v(scope); - int nFormals = ctxt->formalCount(); - for (unsigned i = 0, ei = nFormals; i != ei; ++i) { - QString qName; - if (Identifier *name = ctxt->formals()[nFormals - i - 1]) - qName = name->string; - names->append(qName); - v = ctxt->argument(i); - collector->collect(v); - } - } - }; - - ArgumentCollectJob job(m_engine, collector, names, frameNr, scopeNr); - runInEngine(&job); -} - -/// Same as \c retrieveArgumentsFromContext, but now for locals. -void Debugger::collectLocalsInContext(DataCollector *collector, QStringList *names, int frameNr, - int scopeNr) -{ - Q_ASSERT(collector); - Q_ASSERT(names); - - if (state() != Paused) - return; - - class LocalCollectJob: public Job - { - QV4::ExecutionEngine *engine; - DataCollector *collector; - QStringList *names; - int frameNr; - int scopeNr; - - public: - LocalCollectJob(QV4::ExecutionEngine *engine, DataCollector *collector, QStringList *names, int frameNr, int scopeNr) - : engine(engine) - , collector(collector) - , names(names) - , frameNr(frameNr) - , scopeNr(scopeNr) - {} - - void run() - { - if (frameNr < 0) - return; - - Scope scope(engine); - Scoped<CallContext> ctxt(scope, findScope(findContext(engine->currentContext(), frameNr), scopeNr)); - if (!ctxt) - return; - - ScopedValue v(scope); - for (unsigned i = 0, ei = ctxt->variableCount(); i != ei; ++i) { - QString qName; - if (Identifier *name = ctxt->variables()[i]) - qName = name->string; - names->append(qName); - v = ctxt->d()->locals[i]; - collector->collect(v); - } - } - }; - - LocalCollectJob job(m_engine, collector, names, frameNr, scopeNr); - runInEngine(&job); -} - -bool Debugger::collectThisInContext(DataCollector *collector, int frame) -{ - if (state() != Paused) - return false; - - class ThisCollectJob: public Job - { - QV4::ExecutionEngine *engine; - DataCollector *collector; - int frameNr; - bool *foundThis; - - public: - ThisCollectJob(QV4::ExecutionEngine *engine, DataCollector *collector, int frameNr, - bool *foundThis) - : engine(engine) - , collector(collector) - , frameNr(frameNr) - , foundThis(foundThis) - {} - - void run() - { - *foundThis = myRun(); - } - - bool myRun() - { - Scope scope(engine); - ScopedContext ctxt(scope, findContext(engine->currentContext(), frameNr)); - while (ctxt) { - if (CallContext *cCtxt = ctxt->asCallContext()) - if (cCtxt->d()->activation) - break; - ctxt = ctxt->d()->outer; - } - - if (!ctxt) - return false; - - ScopedValue o(scope, ctxt->asCallContext()->d()->activation); - collector->collect(o); - return true; - } - }; - - bool foundThis = false; - ThisCollectJob job(m_engine, collector, frame, &foundThis); - runInEngine(&job); - return foundThis; -} - -bool Debugger::collectThrownValue(DataCollector *collector) -{ - if (state() != Paused || !m_engine->hasException) - return false; - - class ExceptionCollectJob: public Job - { - QV4::ExecutionEngine *engine; - DataCollector *collector; - - public: - ExceptionCollectJob(QV4::ExecutionEngine *engine, DataCollector *collector) - : engine(engine) - , collector(collector) - {} - - void run() - { - Scope scope(engine); - ScopedValue v(scope, *engine->exceptionValue); - collector->collect(v); - } - }; - - ExceptionCollectJob job(m_engine, collector); - runInEngine(&job); - return true; -} - -QVector<Heap::ExecutionContext::ContextType> Debugger::getScopeTypes(int frame) const -{ - QVector<Heap::ExecutionContext::ContextType> types; - - if (state() != Paused) - return types; - - Scope scope(m_engine); - Scoped<CallContext> sctxt(scope, findContext(m_engine->currentContext(), frame)); - if (!sctxt || sctxt->d()->type < Heap::ExecutionContext::Type_QmlContext) - return types; - - ScopedContext it(scope, sctxt->d()); - for (; it; it = it->d()->outer) - types.append(it->d()->type); - - return types; -} - - -void Debugger::evaluateExpression(int frameNr, const QString &expression, - DataCollector *resultsCollector) -{ - Q_ASSERT(state() == Paused); - - Q_ASSERT(m_runningJob == 0); - ExpressionEvalJob job(m_engine, frameNr, expression, resultsCollector); - runInEngine(&job); -} - void Debugger::maybeBreakAtInstruction() { if (m_runningJob) // do not re-enter when we're doing a job for the debugger. diff --git a/src/qml/jsruntime/qv4debugging_p.h b/src/qml/jsruntime/qv4debugging_p.h index b85611f457..ac0c934d42 100644 --- a/src/qml/jsruntime/qv4debugging_p.h +++ b/src/qml/jsruntime/qv4debugging_p.h @@ -79,73 +79,31 @@ inline bool operator==(const DebuggerBreakPoint &a, const DebuggerBreakPoint &b) typedef QHash<DebuggerBreakPoint, QString> BreakPoints; -class Q_QML_PRIVATE_EXPORT DataCollector -{ -public: - typedef uint Ref; - typedef QVector<uint> Refs; - - DataCollector(QV4::ExecutionEngine *engine); - ~DataCollector(); - - void collect(const QV4::ScopedValue &value); - - QJsonObject lookupRef(Ref ref); - - Ref addFunctionRef(const QString &functionName); - Ref addScriptRef(const QString &scriptName); - - void collectScope(QJsonObject *dict, QV4::Debugging::Debugger *debugger, int frameNr, - int scopeNr); - - QV4::ExecutionEngine *engine() const { return m_engine; } - -private: - friend class RefHolder; - - Ref addRef(QV4::Value value, bool deduplicate = true); - QV4::ReturnedValue getValue(Ref ref); - bool lookupSpecialRef(Ref ref, QJsonObject *dict); - - QJsonArray collectProperties(QV4::Object *object); - QJsonObject collectAsJson(const QString &name, const QV4::ScopedValue &value); - - QV4::ExecutionEngine *m_engine; - Refs *m_collectedRefs; - QV4::PersistentValue values; - typedef QHash<Ref, QJsonObject> SpecialRefs; - SpecialRefs specialRefs; -}; - -class RefHolder { -public: - RefHolder(DataCollector *collector, DataCollector::Refs *target) : - m_collector(collector), m_previousRefs(collector->m_collectedRefs) - { - m_collector->m_collectedRefs = target; - } - - ~RefHolder() - { - std::swap(m_collector->m_collectedRefs, m_previousRefs); - } - -private: - DataCollector *m_collector; - DataCollector::Refs *m_previousRefs; -}; - class Q_QML_EXPORT Debugger : public QObject { Q_OBJECT public: - class Job + class Q_QML_EXPORT Job { public: virtual ~Job() = 0; virtual void run() = 0; }; + class Q_QML_EXPORT JavaScriptJob: public Job + { + QV4::ExecutionEngine *engine; + int frameNr; + const QString &script; + + public: + JavaScriptJob(QV4::ExecutionEngine *engine, int frameNr, const QString &script); + void run(); + + protected: + virtual void handleResult(QV4::ScopedValue &result) = 0; + }; + enum State { Running, Paused @@ -165,7 +123,6 @@ public: ExecutionEngine *engine() const { return m_engine; } - void gatherSources(int requestSequenceNr); void pause(); void resume(Speed speed); @@ -189,16 +146,10 @@ public: } QVector<StackFrame> stackTrace(int frameLimit = -1) const; - void collectArgumentsInContext(DataCollector *collector, QStringList *names, int frameNr = 0, - int scopeNr = 0); - void collectLocalsInContext(DataCollector *collector, QStringList *names, int frameNr = 0, - int scopeNr = 0); - bool collectThisInContext(DataCollector *collector, int frame = 0); - bool collectThrownValue(DataCollector *collector); QVector<Heap::ExecutionContext::ContextType> getScopeTypes(int frame = 0) const; - void evaluateExpression(int frameNr, const QString &expression, - DataCollector *resultsCollector); + Function *getFunction() const; + void runInEngine(Job *job); public: // compile-time interface void maybeBreakAtInstruction(); @@ -213,14 +164,9 @@ signals: void debuggerPaused(QV4::Debugging::Debugger *self, QV4::Debugging::PauseReason reason); private: - Function *getFunction() const; - // requires lock to be held void pauseAndWait(PauseReason reason); - bool reallyHitTheBreakPoint(const QString &filename, int linenr); - - void runInEngine(Job *job); void runInEngine_havingLock(Debugger::Job *job); private: diff --git a/tests/auto/qml/qv4debugger/qv4debugger.pro b/tests/auto/qml/qv4debugger/qv4debugger.pro index 2a318955f3..540cab70e6 100644 --- a/tests/auto/qml/qv4debugger/qv4debugger.pro +++ b/tests/auto/qml/qv4debugger/qv4debugger.pro @@ -2,6 +2,14 @@ CONFIG += testcase TARGET = tst_qv4debugger macx:CONFIG -= app_bundle -SOURCES += tst_qv4debugger.cpp +SOURCES += \ + $$PWD/tst_qv4debugger.cpp \ + $$PWD/../../../../src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp + +HEADERS += \ + $$PWD/../../../../src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h + +INCLUDEPATH += \ + $$PWD/../../../../src/plugins/qmltooling/qmldbg_debugger QT += core-private gui-private qml-private network testlib diff --git a/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp b/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp index ac780d272c..ca308a4f49 100644 --- a/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp +++ b/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp @@ -32,6 +32,8 @@ ****************************************************************************/ #include <QtTest/QtTest> +#include "qv4datacollector.h" + #include <QJSEngine> #include <QQmlEngine> #include <QQmlComponent> @@ -97,14 +99,14 @@ class TestAgent : public QObject { Q_OBJECT public: - typedef QV4::Debugging::DataCollector::Refs Refs; - typedef QV4::Debugging::DataCollector::Ref Ref; + typedef QV4DataCollector::Refs Refs; + typedef QV4DataCollector::Ref Ref; struct NamedRefs { - NamedRefs(DataCollector *collector = 0): collector(collector) {} + NamedRefs(QV4DataCollector *collector = 0): collector(collector) {} QStringList names; Refs refs; - DataCollector *collector; + QV4DataCollector *collector; int size() const { Q_ASSERT(names.size() == refs.size()); @@ -164,13 +166,14 @@ public slots: m_pauseReason = reason; m_statesWhenPaused << debugger->currentExecutionState(); - { + if (debugger->state() == QV4::Debugging::Debugger::Paused && + debugger->engine()->hasException) { Refs refs; - QV4::Debugging::RefHolder holder(&collector, &refs); - if (debugger->collectThrownValue(&collector)) { - Q_ASSERT(refs.size() > 0); - m_thrownValue = refs.first(); - } + RefHolder holder(&collector, &refs); + ExceptionCollectJob job(debugger->engine(), &collector); + debugger->runInEngine(&job); + Q_ASSERT(refs.size() > 0); + m_thrownValue = refs.first(); } foreach (const TestBreakPoint &bp, m_breakPointsToAddWhenPaused) @@ -180,10 +183,13 @@ public slots: m_stackTrace = debugger->stackTrace(); while (!m_expressionRequests.isEmpty()) { + Q_ASSERT(debugger->state() == QV4::Debugging::Debugger::Paused); ExpressionRequest request = m_expressionRequests.takeFirst(); m_expressionResults << Refs(); RefHolder holder(&collector, &m_expressionResults.last()); - debugger->evaluateExpression(request.frameNr, request.expression, &collector); + ExpressionEvalJob job(debugger->engine(), request.frameNr, request.expression, + &collector); + debugger->runInEngine(&job); } if (m_captureContextInfo) @@ -207,11 +213,15 @@ public: for (int i = 0, ei = m_stackTrace.size(); i != ei; ++i) { m_capturedArguments.append(NamedRefs(&collector)); RefHolder argHolder(&collector, &m_capturedArguments.last().refs); - debugger->collectArgumentsInContext(&collector, &m_capturedArguments.last().names, i); + ArgumentCollectJob argumentsJob(debugger->engine(), &collector, + &m_capturedArguments.last().names, i, 0); + debugger->runInEngine(&argumentsJob); m_capturedLocals.append(NamedRefs(&collector)); RefHolder localHolder(&collector, &m_capturedLocals.last().refs); - debugger->collectLocalsInContext(&collector, &m_capturedLocals.last().names, i); + LocalCollectJob localsJob(debugger->engine(), &collector, + &m_capturedLocals.last().names, i, 0); + debugger->runInEngine(&localsJob); } } @@ -234,7 +244,7 @@ public: QVector<NamedRefs> m_capturedArguments; QVector<NamedRefs> m_capturedLocals; qint64 m_thrownValue; - QV4::Debugging::DataCollector collector; + QV4DataCollector collector; struct ExpressionRequest { QString expression; |