diff options
author | Lasse Holmstedt <lasse.holmstedt@nokia.com> | 2010-09-27 17:22:57 +0200 |
---|---|---|
committer | Lasse Holmstedt <lasse.holmstedt@nokia.com> | 2010-09-29 09:39:30 +0200 |
commit | 96d991bbdc34b00b4ba1c3b4f5690f04edfb4cf0 (patch) | |
tree | ba09125bc414c558ad54f1bc2ad391bb0d88779e /share/qtcreator/qml/qmljsdebugger/jsdebuggeragent.cpp | |
parent | 5d9858129aa9c11a9b708c3fb1701c3ed9ac9843 (diff) |
QML Observer: Build together on runtime with debugging tools
Moved qmljsdebugger to a dir under qml/, made qmlobserver compile
without creator dependencies and made it compile with debugging
helpers.
Reviewed-by: hjk
Diffstat (limited to 'share/qtcreator/qml/qmljsdebugger/jsdebuggeragent.cpp')
-rw-r--r-- | share/qtcreator/qml/qmljsdebugger/jsdebuggeragent.cpp | 519 |
1 files changed, 519 insertions, 0 deletions
diff --git a/share/qtcreator/qml/qmljsdebugger/jsdebuggeragent.cpp b/share/qtcreator/qml/qmljsdebugger/jsdebuggeragent.cpp new file mode 100644 index 0000000000..4258882e10 --- /dev/null +++ b/share/qtcreator/qml/qmljsdebugger/jsdebuggeragent.cpp @@ -0,0 +1,519 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSCriptTools module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "jsdebuggeragent.h" + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qset.h> +#include <QtScript/qscriptengine.h> +#include <QtScript/QScriptContextInfo> +#include <QtCore/QDebug> +#include <QtCore/QUrl> +#include <QtCore/QDateTime> +#include <QtScript/qscriptvalueiterator.h> +#include <private/qdeclarativeengine_p.h> + +namespace QmlJSDebugger { + +class JSDebuggerAgent::SetupExecEnv { + JSDebuggerAgent* agent; + JSDebuggerAgent::State previousState; + bool hadException; +public: + SetupExecEnv(JSDebuggerAgent *a) + : agent(a), + previousState(a->state), + hadException(a->engine()->hasUncaughtException()) + { + agent->state = JSDebuggerAgent::Stopped; + } + + ~SetupExecEnv() { + if (!hadException && agent->engine()->hasUncaughtException()) + agent->engine()->clearExceptions(); + agent->state = previousState; + } +}; + +class JSAgentWatchData { +public: + QByteArray exp; + QString name; + QString value; + QString type; + bool hasChildren; + quint64 objectId; + + static JSAgentWatchData fromScriptValue(const QString &expression, const QScriptValue &value) + { + JSAgentWatchData data; + data.exp = expression.toUtf8(); + data.name = expression; + data.hasChildren = false; + data.value = value.toString(); + data.objectId = value.objectId(); + if (value.isArray()) { + data.type = QLatin1String("Array"); + data.value = QString::fromLatin1("[Array of length %1]").arg(value.property("length").toString()); + data.hasChildren = true; + } else if (value.isBool()) { + data.type = QLatin1String("Bool"); +// data.value = value.toBool() ? QLatin1String("true") : QLatin1String("false"); + } else if (value.isDate()) { + data.type = QLatin1String("Date"); + data.value = value.toDateTime().toString(); + } else if (value.isError()) { + data.type = QLatin1String("Error"); + } else if (value.isFunction()) { + data.type = QLatin1String("Function"); + } else if (value.isUndefined()) { + data.type = QLatin1String("<undefined>"); + } else if (value.isNumber()) { + data.type = QLatin1String("Number"); + } else if (value.isRegExp()) { + data.type = QLatin1String("RegExp"); + } else if (value.isString()) { + data.type = QLatin1String("String"); + } else if (value.isVariant()) { + data.type = QLatin1String("Variant"); + } else if (value.isQObject()) { + const QObject *obj = value.toQObject(); + data.value = QString::fromLatin1("[%1]").arg(obj->metaObject()->className()); + data.type = QLatin1String("Object"); + data.hasChildren = true; + } else if (value.isObject()) { + data.type = QLatin1String("Object"); + data.hasChildren = true; + data.type = QLatin1String("Object"); + data.value = QLatin1String("[Object]"); + } else if (value.isNull()) { + data.type = QLatin1String("<null>"); + } else { + data.type = QLatin1String("<unknown>"); + } + return data; + } +}; + + +QDataStream& operator<<(QDataStream& s, const JSAgentWatchData& data) +{ + return s << data.exp << data.name << data.value << data.type << data.hasChildren << data.objectId; +} + +static QList<JSAgentWatchData> expandObject(const QScriptValue &object) +{ + QList<JSAgentWatchData> result; + QScriptValueIterator it(object); + QByteArray expPrefix = '@' + QByteArray::number(object.objectId(), 16) + "->"; + while (it.hasNext()) { + it.next(); + if (it.flags() & QScriptValue::SkipInEnumeration) + continue; + if (object.isQObject() && it.value().isFunction()) { + // cosmetics: skip all signals and slot, there is too many of them, + // and it is not usefull in the debugger. + continue; + } + JSAgentWatchData data = JSAgentWatchData::fromScriptValue(it.name(), it.value()); + data.exp.prepend(expPrefix); + result << data; + } + return result; +} + +void JSDebuggerAgent::recordKnownObjects(const QList<JSAgentWatchData>& list) +{ + foreach (const JSAgentWatchData &data, list) + knownObjectIds << data.objectId; +} + +QList<JSAgentWatchData> JSDebuggerAgent::getLocals(QScriptContext *ctx) +{ + QList<JSAgentWatchData> locals; + if (ctx) { + QScriptValue activationObject = ctx->activationObject(); + QScriptValue thisObject = ctx->thisObject(); + locals = expandObject(activationObject); + if (thisObject.isObject() && thisObject.objectId() != engine()->globalObject().objectId()) + locals.prepend(JSAgentWatchData::fromScriptValue("this", thisObject)); + recordKnownObjects(locals); + knownObjectIds << activationObject.objectId(); + } + return locals; +} + +/*! + Constructs a new agent for the given \a engine. The agent will + report debugging-related events (e.g. step completion) to the given + \a backend. +*/ +JSDebuggerAgent::JSDebuggerAgent(QScriptEngine *engine) + : QDeclarativeDebugService("JSDebugger"), QScriptEngineAgent(engine) + , state(NoState) +{} + +JSDebuggerAgent::JSDebuggerAgent(QDeclarativeEngine *engine) + : QDeclarativeDebugService("JSDebugger") + , QScriptEngineAgent(QDeclarativeEnginePrivate::getScriptEngine(engine)) + , state(NoState) +{} + +/*! + Destroys this QScriptDebuggerAgent. +*/ +JSDebuggerAgent::~JSDebuggerAgent() +{} + +/*! + \reimp +*/ +void JSDebuggerAgent::scriptLoad(qint64 id, const QString & program, + const QString &fileName, int ) +{ + Q_UNUSED(program); + filenames.insert(id, QUrl(fileName).toLocalFile()); +} + +/*! + \reimp +*/ +void JSDebuggerAgent::scriptUnload(qint64 id) +{ + filenames.remove(id); +} + +/*! + \reimp +*/ +void JSDebuggerAgent::contextPush() +{ +} + +/*! + \reimp +*/ +void JSDebuggerAgent::contextPop() +{ +} + +/*! + \reimp +*/ +void JSDebuggerAgent::functionEntry(qint64 scriptId) +{ + Q_UNUSED(scriptId); + stepDepth++; +} + +/*! + \reimp +*/ +void JSDebuggerAgent::functionExit(qint64 scriptId, const QScriptValue &returnValue) +{ + Q_UNUSED(scriptId); + Q_UNUSED(returnValue); + stepDepth--; +} + +/*! + \reimp +*/ +void JSDebuggerAgent::positionChange(qint64 scriptId, + int lineNumber, int columnNumber) +{ + Q_UNUSED(columnNumber); + + if(state == Stopped) + return; //no re-entrency + + // check breakpoints + if (!breakpointList.isEmpty()) { + QHash<qint64, QString>::const_iterator it = filenames.constFind(scriptId); + if (it == filenames.constEnd()) { + // It is possible that the scripts are loaded before the agent is attached + QString filename = QUrl(QScriptContextInfo(engine()->currentContext()).fileName()).toLocalFile(); + QPair<QString, qint32> key = qMakePair(filename, lineNumber); + it = filenames.insert(scriptId, filename); + } + QPair<QString, qint32> key = qMakePair(*it, lineNumber); + if (breakpointList.contains(key)) { + stopped(); + return; + } + } + + switch (state) { + case NoState: + case Stopped: + // Do nothing + break; + case SteppingOutState: + if (stepDepth >= 0) + break; + //fallthough + case SteppingOverState: + if (stepDepth > 0) + break; + //fallthough + case SteppingIntoState: + stopped(); + break; + } + +} + +/*! + \reimp +*/ +void JSDebuggerAgent::exceptionThrow(qint64 scriptId, + const QScriptValue &exception, + bool hasHandler) +{ + Q_UNUSED(scriptId); + Q_UNUSED(exception); + Q_UNUSED(hasHandler); +// qDebug() << Q_FUNC_INFO << exception.toString() << hasHandler; +#if 0 //sometimes, we get exceptions that we should just ignore. + if (!hasHandler && state != Stopped) + stopped(true, exception); +#endif +} + +/*! + \reimp +*/ +void JSDebuggerAgent::exceptionCatch(qint64 scriptId, + const QScriptValue &exception) +{ + Q_UNUSED(scriptId); + Q_UNUSED(exception); +} + +bool JSDebuggerAgent::supportsExtension(QScriptEngineAgent::Extension extension) const +{ + return extension == QScriptEngineAgent::DebuggerInvocationRequest; +} + +QVariant JSDebuggerAgent::extension(QScriptEngineAgent::Extension extension, const QVariant& argument) +{ + if (extension == QScriptEngineAgent::DebuggerInvocationRequest) { + stopped(); + return QVariant(); + } + return QScriptEngineAgent::extension(extension, argument); +} + + + +void JSDebuggerAgent::messageReceived(const QByteArray& message) +{ + QDataStream ds(message); + QByteArray command; + ds >> command; + if (command == "BREAKPOINTS") { + ds >> breakpointList; + } else if (command == "WATCH_EXPRESSIONS") { + ds >> watchExpressions; + } else if (command == "STEPOVER") { + stepDepth = 0; + state = SteppingOverState; + continueExec(); + } else if (command == "STEPINTO" || command == "INTERRUPT") { + stepDepth = 0; + state = SteppingIntoState; + continueExec(); + } else if (command == "STEPOUT") { + stepDepth = 0; + state = SteppingOutState; + continueExec(); + } else if (command == "CONTINUE") { + state = NoState; + continueExec(); + } else if (command == "EXEC") { + SetupExecEnv execEnv(this); + + QByteArray id; + QString expr; + ds >> id >> expr; + + JSAgentWatchData data = JSAgentWatchData::fromScriptValue(expr, engine()->evaluate(expr)); + knownObjectIds << data.objectId; + + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + rs << QByteArray("RESULT") << id << data; + sendMessage(reply); + } else if (command == "EXPAND") { + SetupExecEnv execEnv(this); + + QByteArray requestId; + quint64 objectId; + ds >> requestId >> objectId; + QScriptValue v; + if (knownObjectIds.contains(objectId)) + v = engine()->objectById(objectId); + + QList<JSAgentWatchData> result = expandObject(v); + recordKnownObjects(result); + + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + rs << QByteArray("EXPANDED") << requestId << result; + sendMessage(reply); + + } else if (command == "ACTIVATE_FRAME") { + SetupExecEnv execEnv(this); + + int frameId; + ds >> frameId; + + int deep = 0; + QScriptContext *ctx = engine()->currentContext(); + while (ctx && deep < frameId) { + ctx = ctx->parentContext(); + deep++; + } + + QList<JSAgentWatchData> locals = getLocals(ctx); + + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + rs << QByteArray("LOCALS") << frameId << locals; + sendMessage(reply); + } else if (command == "SET_PROPERTY") { + SetupExecEnv execEnv(this); + + QByteArray id; + qint64 objectId; + QString property; + QString value; + ds >> id >> objectId >> property >> value; + + if (knownObjectIds.contains(objectId)) { + QScriptValue object; + object = engine()->objectById(objectId); + + if(object.isObject()) { + QScriptValue result = engine()->evaluate(value); + object.setProperty(property, result); + } + } + + //TODO: feedback + } else if (command == "PING") { + int ping; + ds >> ping; + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + rs << QByteArray("PONG") << ping; + sendMessage(reply); + } else { + qDebug() << Q_FUNC_INFO << "Unknown command" << command; + } + + QDeclarativeDebugService::messageReceived(message); +} + +void JSDebuggerAgent::stopped(bool becauseOfException, const QScriptValue& exception) +{ + knownObjectIds.clear(); + state = Stopped; + QList<QPair<QString, QPair<QString, qint32> > > backtrace; + + for (QScriptContext* ctx = engine()->currentContext(); ctx; ctx = ctx->parentContext()) { + QScriptContextInfo info(ctx); + + QString functionName = info.functionName(); + if (functionName.isEmpty()) { + if (ctx->parentContext()) { + switch (info.functionType()) { + case QScriptContextInfo::ScriptFunction: + functionName = QLatin1String("<anonymous>"); + break; + case QScriptContextInfo::NativeFunction: + functionName = QLatin1String("<native>"); + break; + case QScriptContextInfo::QtFunction: + case QScriptContextInfo::QtPropertyFunction: + functionName = QLatin1String("<native slot>"); + break; + } + } else { + functionName = QLatin1String("<global>"); + } + } + int lineNumber = info.lineNumber(); + if (lineNumber == -1) // if the line number is unknown, fallback to the function line number + lineNumber = info.functionStartLineNumber(); + backtrace.append(qMakePair(functionName, qMakePair( QUrl(info.fileName()).toLocalFile(), lineNumber ) ) ); + } + QList<JSAgentWatchData> watches; + foreach (const QString &expr, watchExpressions) + watches << JSAgentWatchData::fromScriptValue(expr, engine()->evaluate(expr)); + recordKnownObjects(watches); + + QList<JSAgentWatchData> locals = getLocals(engine()->currentContext()); + + if (!becauseOfException) { + // Clear any exceptions occurred during locals evaluation. + engine()->clearExceptions(); + } + + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + rs << QByteArray("STOPPED") << backtrace << watches << locals << becauseOfException << exception.toString(); + sendMessage(reply); + + loop.exec(QEventLoop::ExcludeUserInputEvents); +} + +void JSDebuggerAgent::continueExec() +{ + loop.quit(); +} + +void JSDebuggerAgent::enabledChanged(bool on) +{ + engine()->setAgent(on ? this : 0); +} + +} // namespace QmlJSDebugger |