aboutsummaryrefslogtreecommitdiffstats
path: root/share/qtcreator/qml/qmljsdebugger/jsdebuggeragent.cpp
diff options
context:
space:
mode:
authorLasse Holmstedt <lasse.holmstedt@nokia.com>2010-09-27 17:22:57 +0200
committerLasse Holmstedt <lasse.holmstedt@nokia.com>2010-09-29 09:39:30 +0200
commit96d991bbdc34b00b4ba1c3b4f5690f04edfb4cf0 (patch)
treeba09125bc414c558ad54f1bc2ad391bb0d88779e /share/qtcreator/qml/qmljsdebugger/jsdebuggeragent.cpp
parent5d9858129aa9c11a9b708c3fb1701c3ed9ac9843 (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.cpp519
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