/**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of Qt Creator. ** ** 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 Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "qmlv8debuggerclient.h" #include "qmlv8debuggerclientconstants.h" #include "debuggerstringutils.h" #include "watchhandler.h" #include "breakpoint.h" #include "breakhandler.h" #include "qmlengine.h" #include "stackhandler.h" #include "qtmessageloghandler.h" #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_QML 0 #if DEBUG_QML # define SDEBUG(s) qDebug() << s << '\n' #else # define SDEBUG(s) #endif using namespace Core; namespace Debugger { namespace Internal { typedef QPair WatchDataPair; struct QmlV8ObjectData { int handle; QByteArray name; QByteArray type; QVariant value; QVariantList properties; }; class QmlV8DebuggerClientPrivate { public: explicit QmlV8DebuggerClientPrivate(QmlV8DebuggerClient *q) : q(q), sequence(-1), engine(0), previousStepAction(QmlV8DebuggerClient::Continue) { parser = m_scriptEngine.evaluate(_("JSON.parse")); stringifier = m_scriptEngine.evaluate(_("JSON.stringify")); } void connect(); void disconnect(); void interrupt(); void continueDebugging(QmlV8DebuggerClient::StepAction stepAction, int stepCount = 1); void evaluate(const QString expr, bool global = false, bool disableBreak = false, int frame = -1, bool addContext = false); void lookup(const QList handles, bool includeSource = false); void backtrace(int fromFrame = -1, int toFrame = -1, bool bottom = false); void frame(int number = -1); void scope(int number = -1, int frameNumber = -1); void scopes(int frameNumber = -1); void scripts(int types = 4, const QList ids = QList(), bool includeSource = false, const QVariant filter = QVariant()); void source(int frame = -1, int fromLine = -1, int toLine = -1); void setBreakpoint(const QString type, const QString target, bool enabled = true,int line = 0, int column = 0, const QString condition = QString(), int ignoreCount = -1); void changeBreakpoint(int breakpoint, bool enabled = true, const QString condition = QString(), int ignoreCount = -1); void clearBreakpoint(int breakpoint); void setExceptionBreak(QmlV8DebuggerClient::Exceptions type, bool enabled = false); void listBreakpoints(); void v8flags(const QString flags); void version(); //void profile(ProfileCommand command); //NOT SUPPORTED void gc(); QmlV8ObjectData extractData(const QVariant &data, const QVariant &refsVal); void clearCache(); void logSendMessage(const QString &msg) const; void logReceiveMessage(const QString &msg) const; QtMessageLogItem *constructLogItemTree(QtMessageLogItem *parent, const QmlV8ObjectData &objectData, const QVariant &refsVal); private: QByteArray packMessage(const QByteArray &type, const QByteArray &message = QByteArray()); QScriptValue initObject(); QVariant valueFromRef(int handle, const QVariant &refsVal, bool *success); public: QmlV8DebuggerClient *q; int sequence; QmlEngine *engine; QHash breakpoints; QHash breakpointsSync; QList breakpointsTemp; QScriptValue parser; QScriptValue stringifier; QStringList scriptSourceRequests; QHash evaluatingExpression; QHash localsAndWatchers; QList updateLocalsAndWatchers; QList debuggerCommands; //Cache QStringList watchedExpressions; QList currentFrameScopes; QHash stackIndexLookup; QmlV8DebuggerClient::StepAction previousStepAction; private: QScriptEngine m_scriptEngine; }; /////////////////////////////////////////////////////////////////////// // // QmlV8DebuggerClientPrivate // /////////////////////////////////////////////////////////////////////// void QmlV8DebuggerClientPrivate::connect() { logSendMessage(QString(_("%1 %2")).arg(_(V8DEBUG), _(CONNECT))); q->sendMessage(packMessage(CONNECT)); } void QmlV8DebuggerClientPrivate::disconnect() { // { "seq" : , // "type" : "request", // "command" : "disconnect", // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(DISCONNECT))); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2")).arg(_(V8DEBUG), jsonMessage.toString())); q->sendMessage(packMessage(DISCONNECT, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::interrupt() { logSendMessage(QString(_("%1 %2")).arg(_(V8DEBUG), _(INTERRUPT))); q->sendMessage(packMessage(INTERRUPT)); } void QmlV8DebuggerClientPrivate::continueDebugging(QmlV8DebuggerClient::StepAction action, int count) { // { "seq" : , // "type" : "request", // "command" : "continue", // "arguments" : { "stepaction" : <"in", "next" or "out">, // "stepcount" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(CONTINEDEBUGGING))); if (action != QmlV8DebuggerClient::Continue) { QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); switch (action) { case QmlV8DebuggerClient::In: args.setProperty(_(STEPACTION), QScriptValue(_(IN))); break; case QmlV8DebuggerClient::Out: args.setProperty(_(STEPACTION), QScriptValue(_(OUT))); break; case QmlV8DebuggerClient::Next: args.setProperty(_(STEPACTION), QScriptValue(_(NEXT))); break; default:break; } if (count != 1) args.setProperty(_(STEPCOUNT), QScriptValue(count)); jsonVal.setProperty(_(ARGUMENTS), args); } const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); previousStepAction = action; } void QmlV8DebuggerClientPrivate::evaluate(const QString expr, bool global, bool disableBreak, int frame, bool addContext) { // { "seq" : , // "type" : "request", // "command" : "evaluate", // "arguments" : { "expression" : , // "frame" : , // "global" : , // "disable_break" : , // "additional_context" : [ // { "name" : , "handle" : }, // { "name" : , "handle" : }, // ... // ] // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(EVALUATE))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(EXPRESSION), QScriptValue(expr)); if (frame != -1) args.setProperty(_(FRAME), QScriptValue(frame)); if (global) args.setProperty(_(GLOBAL), QScriptValue(global)); if (disableBreak) args.setProperty(_(DISABLE_BREAK), QScriptValue(disableBreak)); if (addContext) { QAbstractItemModel *localsModel = engine->localsModel(); int rowCount = localsModel->rowCount(); QScriptValue ctxtList = parser.call(QScriptValue(), QScriptValueList() << _(ARRAY )); while (rowCount) { QModelIndex index = localsModel->index(--rowCount, 0); const WatchData *data = engine->watchHandler()->watchData(index); QScriptValue ctxt = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); ctxt.setProperty(_(NAME), QScriptValue(data->name)); ctxt.setProperty(_(HANDLE), QScriptValue(int(data->id))); ctxtList.setProperty(rowCount, ctxt); } args.setProperty(_(ADDITIONAL_CONTEXT), QScriptValue(ctxtList)); } jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::lookup(QList handles, bool includeSource) { // { "seq" : , // "type" : "request", // "command" : "lookup", // "arguments" : { "handles" : , // "includeSource" : , // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(LOOKUP))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); QScriptValue array = parser.call(QScriptValue(), QScriptValueList() << _(ARRAY)); int index = 0; foreach (int handle, handles) { array.setProperty(index++, QScriptValue(handle)); } args.setProperty(_(HANDLES), array); if (includeSource) args.setProperty(_(INCLUDESOURCE), QScriptValue(includeSource)); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::backtrace(int fromFrame, int toFrame, bool bottom) { // { "seq" : , // "type" : "request", // "command" : "backtrace", // "arguments" : { "fromFrame" : // "toFrame" : // "bottom" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(BACKTRACE))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); if (fromFrame != -1) args.setProperty(_(FROMFRAME), QScriptValue(fromFrame)); if (toFrame != -1) args.setProperty(_(TOFRAME), QScriptValue(toFrame)); if (bottom) args.setProperty(_(BOTTOM), QScriptValue(bottom)); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::frame(int number) { // { "seq" : , // "type" : "request", // "command" : "frame", // "arguments" : { "number" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(FRAME))); if (number != -1) { QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(NUMBER), QScriptValue(number)); jsonVal.setProperty(_(ARGUMENTS), args); } const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::scope(int number, int frameNumber) { // { "seq" : , // "type" : "request", // "command" : "scope", // "arguments" : { "number" : // "frameNumber" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(SCOPE))); if (number != -1) { QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(NUMBER), QScriptValue(number)); if (frameNumber != -1) args.setProperty(_(FRAMENUMBER), QScriptValue(frameNumber)); jsonVal.setProperty(_(ARGUMENTS), args); } const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::scopes(int frameNumber) { // { "seq" : , // "type" : "request", // "command" : "scopes", // "arguments" : { "frameNumber" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(SCOPES))); if (frameNumber != -1) { QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(FRAMENUMBER), QScriptValue(frameNumber)); jsonVal.setProperty(_(ARGUMENTS), args); } const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::scripts(int types, const QList ids, bool includeSource, const QVariant filter) { // { "seq" : , // "type" : "request", // "command" : "scripts", // "arguments" : { "types" : // "ids" : // "includeSource" : // "filter" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(SCRIPTS))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(TYPES), QScriptValue(types)); if (ids.count()) { QScriptValue array = parser.call(QScriptValue(), QScriptValueList() << _(ARRAY)); int index = 0; foreach (int id, ids) { array.setProperty(index++, QScriptValue(id)); } args.setProperty(_(IDS), array); } if (includeSource) args.setProperty(_(INCLUDESOURCE), QScriptValue(includeSource)); QScriptValue filterValue; if (filter.type() == QVariant::String) filterValue = QScriptValue(filter.toString()); else if (filter.type() == QVariant::Int) filterValue = QScriptValue(filter.toInt()); else QTC_CHECK(!filter.isValid()); args.setProperty(_(FILTER), filterValue); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::source(int frame, int fromLine, int toLine) { // { "seq" : , // "type" : "request", // "command" : "source", // "arguments" : { "frame" : // "fromLine" : // "toLine" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(SOURCE))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); if (frame != -1) args.setProperty(_(FRAME), QScriptValue(frame)); if (fromLine != -1) args.setProperty(_(FROMLINE), QScriptValue(fromLine)); if (toLine != -1) args.setProperty(_(TOLINE), QScriptValue(toLine)); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::setBreakpoint(const QString type, const QString target, bool enabled, int line, int column, const QString condition, int ignoreCount) { // { "seq" : , // "type" : "request", // "command" : "setbreakpoint", // "arguments" : { "type" : <"function" or "script" or "scriptId" or "scriptRegExp"> // "target" : // "line" : // "column" : // "enabled" : // "condition" : // "ignoreCount" : // } // } if (type == _(EVENT)) { QByteArray params; QDataStream rs(¶ms, QIODevice::WriteOnly); rs << target.toUtf8() << enabled; logSendMessage(QString(_("%1 %2 %3 %4")).arg(_(V8DEBUG), _(BREAKONSIGNAL), target, enabled?_("enabled"):_("disabled"))); q->sendMessage(packMessage(BREAKONSIGNAL, params)); } else { QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(SETBREAKPOINT))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(TYPE), QScriptValue(type)); if (type == _(SCRIPTREGEXP)) args.setProperty(_(TARGET), QScriptValue(QFileInfo(target).fileName())); else args.setProperty(_(TARGET), QScriptValue(target)); if (line) args.setProperty(_(LINE), QScriptValue(line - 1)); if (column) args.setProperty(_(COLUMN), QScriptValue(column - 1)); args.setProperty(_(ENABLED), QScriptValue(enabled)); if (!condition.isEmpty()) args.setProperty(_(CONDITION), QScriptValue(condition)); if (ignoreCount != -1) args.setProperty(_(IGNORECOUNT), QScriptValue(ignoreCount)); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } } void QmlV8DebuggerClientPrivate::changeBreakpoint(int breakpoint, bool enabled, const QString condition, int ignoreCount) { // { "seq" : , // "type" : "request", // "command" : "changebreakpoint", // "arguments" : { "breakpoint" : // "enabled" : // "condition" : // "ignoreCount" : sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::clearBreakpoint(int breakpoint) { // { "seq" : , // "type" : "request", // "command" : "clearbreakpoint", // "arguments" : { "breakpoint" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(CLEARBREAKPOINT))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(BREAKPOINT), QScriptValue(breakpoint)); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::setExceptionBreak(QmlV8DebuggerClient::Exceptions type, bool enabled) { // { "seq" : , // "type" : "request", // "command" : "setexceptionbreak", // "arguments" : { "type" : , // "enabled" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(SETEXCEPTIONBREAK))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); if (type == QmlV8DebuggerClient::AllExceptions) args.setProperty(_(TYPE), QScriptValue(_(ALL))); //Not Supported // else if (type == QmlV8DebuggerClient::UncaughtExceptions) // args.setProperty(_(TYPE),QScriptValue(_(UNCAUGHT))); if (enabled) args.setProperty(_(ENABLED), QScriptValue(enabled)); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::listBreakpoints() { // { "seq" : , // "type" : "request", // "command" : "listbreakpoints", // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(LISTBREAKPOINTS))); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::v8flags(const QString flags) { // { "seq" : , // "type" : "request", // "command" : "v8flags", // "arguments" : { "flags" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(V8FLAGS))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(FLAGS), QScriptValue(flags)); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::version() { // { "seq" : , // "type" : "request", // "command" : "version", // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(VERSION))); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } //void QmlV8DebuggerClientPrivate::profile(ProfileCommand command) //{ //// { "seq" : , //// "type" : "request", //// "command" : "profile", //// "arguments" : { "command" : "resume" or "pause" } //// } // QScriptValue jsonVal = initObject(); // jsonVal.setProperty(_(COMMAND), QScriptValue(_(PROFILE))); // QScriptValue args = m_parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); // if (command == Resume) // args.setProperty(_(COMMAND), QScriptValue(_(RESUME))); // else // args.setProperty(_(COMMAND), QScriptValue(_(PAUSE))); // args.setProperty(_("modules"), QScriptValue(1)); // jsonVal.setProperty(_(ARGUMENTS), args); // const QScriptValue jsonMessage = m_stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); // logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); // q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); //} void QmlV8DebuggerClientPrivate::gc() { // { "seq" : , // "type" : "request", // "command" : "gc", // "arguments" : { "type" : , // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(GARBAGECOLLECTOR))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(TYPE), QScriptValue(_(ALL))); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } QmlV8ObjectData QmlV8DebuggerClientPrivate::extractData(const QVariant &data, const QVariant &refsVal) { // { "handle" : , // "type" : <"undefined", "null", "boolean", "number", "string", "object", "function" or "frame"> // } // {"handle":,"type":"undefined"} // {"handle":,"type":"null"} // { "handle":, // "type" : <"boolean", "number" or "string"> // "value" : // } // {"handle":7,"type":"boolean","value":true} // {"handle":8,"type":"number","value":42} // { "handle" : , // "type" : "object", // "className" : , // "constructorFunction" : {"ref":}, // "protoObject" : {"ref":}, // "prototypeObject" : {"ref":}, // "properties" : [ {"name" : , // "ref" : // }, // ... // ] // } // { "handle" : , // "type" : "function", // "className" : "Function", // "constructorFunction" : {"ref":}, // "protoObject" : {"ref":}, // "prototypeObject" : {"ref":}, // "name" : , // "inferredName" : // "source" : , // "script" : , // "scriptId" : , // "position" : , // "line" : , // "column" : , // "properties" : [ {"name" : , // "ref" : // }, // ... // ] // } QmlV8ObjectData objectData; const QVariantMap dataMap = data.toMap(); objectData.name = dataMap.value(_(NAME)).toByteArray(); if (dataMap.contains(_(REF))) { objectData.handle = dataMap.value(_(REF)).toInt(); bool success; QVariant dataFromRef = valueFromRef(objectData.handle, refsVal, &success); if (success) { QmlV8ObjectData data = extractData(dataFromRef, refsVal); objectData.type = data.type; objectData.value = data.value; objectData.properties = data.properties; } } else { objectData.handle = dataMap.value(_(HANDLE)).toInt(); QString type = dataMap.value(_(TYPE)).toString(); if (type == _("undefined")) { objectData.type = QByteArray("undefined"); objectData.value = QVariant(_("undefined")); } else if (type == _("null")) { objectData.type = QByteArray("null"); objectData.value= QVariant(_("null")); } else if (type == _("boolean")) { objectData.type = QByteArray("boolean"); objectData.value = dataMap.value(_(VALUE)); } else if (type == _("number")) { objectData.type = QByteArray("number"); objectData.value = dataMap.value(_(VALUE)); } else if (type == _("string")) { objectData.type = QByteArray("string"); objectData.value = dataMap.value(_(VALUE)); } else if (type == _("object")) { objectData.type = QByteArray("object"); objectData.value = dataMap.value(_("className")); objectData.properties = dataMap.value(_("properties")).toList(); } else if (type == _("function")) { objectData.type = QByteArray("function"); objectData.value = dataMap.value(_(NAME)); objectData.properties = dataMap.value(_("properties")).toList(); } else if (type == _("script")) { objectData.type = QByteArray("script"); objectData.value = dataMap.value(_(NAME)); } } return objectData; } void QmlV8DebuggerClientPrivate::clearCache() { watchedExpressions.clear(); currentFrameScopes.clear(); evaluatingExpression.clear(); updateLocalsAndWatchers.clear(); } QByteArray QmlV8DebuggerClientPrivate::packMessage(const QByteArray &type, const QByteArray &message) { SDEBUG(message); QByteArray request; QDataStream rs(&request, QIODevice::WriteOnly); QByteArray cmd = V8DEBUG; rs << cmd << type << message; return request; } QScriptValue QmlV8DebuggerClientPrivate::initObject() { QScriptValue jsonVal = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); jsonVal.setProperty(_(SEQ), QScriptValue(++sequence)); jsonVal.setProperty(_(TYPE), _(REQUEST)); return jsonVal; } QVariant QmlV8DebuggerClientPrivate::valueFromRef(int handle, const QVariant &refsVal, bool *success) { *success = false; QVariant variant; const QVariantList refs = refsVal.toList(); foreach (const QVariant &ref, refs) { const QVariantMap refData = ref.toMap(); if (refData.value(_(HANDLE)).toInt() == handle) { variant = refData; *success = true; break; } } return variant; } void QmlV8DebuggerClientPrivate::logSendMessage(const QString &msg) const { if (engine) engine->logMessage(QLatin1String("V8DebuggerClient"), QmlEngine::LogSend, msg); } void QmlV8DebuggerClientPrivate::logReceiveMessage(const QString &msg) const { if (engine) engine->logMessage(QLatin1String("V8DebuggerClient"), QmlEngine::LogReceive, msg); } QtMessageLogItem *QmlV8DebuggerClientPrivate::constructLogItemTree( QtMessageLogItem *parent, const QmlV8ObjectData &objectData, const QVariant &refsVal) { if (!objectData.value.isValid()) return 0; QString text; if (objectData.name.isEmpty()) text = objectData.value.toString(); else text = QString(_("%1: %2")).arg(QString::fromAscii(objectData.name)) .arg(objectData.value.toString()); QtMessageLogItem *item = new QtMessageLogItem(parent, QtMessageLogHandler::UndefinedType, text); foreach (const QVariant &property, objectData.properties) { QtMessageLogItem *child = constructLogItemTree( item, extractData(property, refsVal), refsVal); if (child) item->insertChild(child); } return item; } /////////////////////////////////////////////////////////////////////// // // QmlV8DebuggerClient // /////////////////////////////////////////////////////////////////////// QmlV8DebuggerClient::QmlV8DebuggerClient(QmlDebug::QmlDebugConnection *client) : BaseQmlDebuggerClient(client, QLatin1String("V8Debugger")), d(new QmlV8DebuggerClientPrivate(this)) { } QmlV8DebuggerClient::~QmlV8DebuggerClient() { delete d; } void QmlV8DebuggerClient::startSession() { flushSendBuffer(); d->connect(); //Query for the V8 version. This is //only for logging to the debuggerlog d->version(); } void QmlV8DebuggerClient::endSession() { d->disconnect(); } void QmlV8DebuggerClient::executeStep() { clearExceptionSelection(); d->continueDebugging(In); } void QmlV8DebuggerClient::executeStepOut() { clearExceptionSelection(); d->continueDebugging(Out); } void QmlV8DebuggerClient::executeNext() { clearExceptionSelection(); d->continueDebugging(Next); } void QmlV8DebuggerClient::executeStepI() { clearExceptionSelection(); d->continueDebugging(In); } void QmlV8DebuggerClient::executeRunToLine(const ContextData &data) { d->setBreakpoint(QString(_(SCRIPTREGEXP)), data.fileName, true, data.lineNumber); clearExceptionSelection(); d->continueDebugging(Continue); } void QmlV8DebuggerClient::continueInferior() { clearExceptionSelection(); d->continueDebugging(Continue); } void QmlV8DebuggerClient::interruptInferior() { d->interrupt(); } void QmlV8DebuggerClient::activateFrame(int index) { if (index != d->engine->stackHandler()->currentIndex()) d->frame(d->stackIndexLookup.value(index)); d->engine->stackHandler()->setCurrentIndex(index); } bool QmlV8DebuggerClient::acceptsBreakpoint(const BreakpointModelId &id) { BreakpointType type = d->engine->breakHandler()->breakpointData(id).type; return (type == BreakpointOnQmlSignalEmit || type == BreakpointByFileAndLine || type == BreakpointAtJavaScriptThrow); } void QmlV8DebuggerClient::insertBreakpoint(const BreakpointModelId &id, int adjustedLine, int adjustedColumn) { BreakHandler *handler = d->engine->breakHandler(); const BreakpointParameters ¶ms = handler->breakpointData(id); if (params.type == BreakpointAtJavaScriptThrow) { handler->notifyBreakpointInsertOk(id); d->setExceptionBreak(AllExceptions, params.enabled); } else if (params.type == BreakpointByFileAndLine) { d->setBreakpoint(QString(_(SCRIPTREGEXP)), params.fileName, params.enabled, adjustedLine, adjustedColumn, QLatin1String(params.condition), params.ignoreCount); } else if (params.type == BreakpointOnQmlSignalEmit) { d->setBreakpoint(QString(_(EVENT)), params.functionName, params.enabled); d->engine->breakHandler()->notifyBreakpointInsertOk(id); } d->breakpointsSync.insert(d->sequence, id); } void QmlV8DebuggerClient::removeBreakpoint(const BreakpointModelId &id) { BreakHandler *handler = d->engine->breakHandler(); const BreakpointParameters ¶ms = handler->breakpointData(id); int breakpoint = d->breakpoints.value(id); d->breakpoints.remove(id); if (params.type == BreakpointAtJavaScriptThrow) d->setExceptionBreak(AllExceptions); else if (params.type == BreakpointOnQmlSignalEmit) d->setBreakpoint(QString(_(EVENT)), params.functionName, false); else d->clearBreakpoint(breakpoint); } void QmlV8DebuggerClient::changeBreakpoint(const BreakpointModelId &id) { BreakHandler *handler = d->engine->breakHandler(); const BreakpointParameters ¶ms = handler->breakpointData(id); BreakpointResponse br = handler->response(id); if (params.type == BreakpointAtJavaScriptThrow) { d->setExceptionBreak(AllExceptions, params.enabled); br.enabled = params.enabled; handler->setResponse(id, br); } else if (params.type == BreakpointOnQmlSignalEmit) { d->setBreakpoint(QString(_(EVENT)), params.functionName, params.enabled); br.enabled = params.enabled; handler->setResponse(id, br); } else { //V8 supports only minimalistic changes in breakpoint //Remove the breakpoint and add again handler->notifyBreakpointChangeOk(id); handler->removeBreakpoint(id); handler->appendBreakpoint(params); } } void QmlV8DebuggerClient::synchronizeBreakpoints() { //NOT USED } void QmlV8DebuggerClient::assignValueInDebugger(const WatchData * /*data*/, const QString &expr, const QVariant &valueV) { StackHandler *stackHandler = d->engine->stackHandler(); QString expression = QString(_("%1 = %2;")).arg(expr).arg(valueV.toString()); if (stackHandler->isContentsValid() && stackHandler->currentFrame().isUsable()) { d->evaluate(expression, false, false, stackHandler->currentIndex()); d->updateLocalsAndWatchers.append(d->sequence); } else { d->engine->showMessage(QString(_("Cannot evaluate" "%1 in current stack frame")). arg(expression), QtMessageLogOutput); } } void QmlV8DebuggerClient::updateWatchData(const WatchData &/*data*/) { //NOT USED } void QmlV8DebuggerClient::executeDebuggerCommand(const QString &command) { StackHandler *stackHandler = d->engine->stackHandler(); if (stackHandler->isContentsValid() && stackHandler->currentFrame().isUsable()) { d->evaluate(command, false, false, stackHandler->currentIndex()); d->debuggerCommands.append(d->sequence); } else { //Currently cannot evaluate if not in a javascript break d->engine->showMessage(QString(_("Cannot evaluate %1" "in current stack frame")). arg(command), QtMessageLogOutput); } } void QmlV8DebuggerClient::synchronizeWatchers(const QStringList &watchers) { SDEBUG(watchers); foreach (const QString &exp, watchers) { if (!d->watchedExpressions.contains(exp)) { StackHandler *stackHandler = d->engine->stackHandler(); if (stackHandler->isContentsValid() && stackHandler->currentFrame().isUsable()) { d->evaluate(exp, false, false, stackHandler->currentIndex()); d->evaluatingExpression.insert(d->sequence, exp); } } } d->watchedExpressions = watchers; } void QmlV8DebuggerClient::expandObject(const QByteArray &iname, quint64 objectId) { if (objectId == 0) { //We may have got the global object const WatchData *watch = d->engine->watchHandler()->findData(iname); if (watch->value == QLatin1String("global")) { StackHandler *stackHandler = d->engine->stackHandler(); if (stackHandler->isContentsValid() && stackHandler->currentFrame().isUsable()) { d->evaluate(watch->name, false, false, stackHandler->currentIndex()); d->evaluatingExpression.insert(d->sequence, QLatin1String(iname)); } return; } } d->localsAndWatchers.insertMulti(objectId, iname); d->lookup(QList() << objectId); } void QmlV8DebuggerClient::setEngine(QmlEngine *engine) { d->engine = engine; } void QmlV8DebuggerClient::getSourceFiles() { d->scripts(4, QList(), true, QVariant()); } void QmlV8DebuggerClient::messageReceived(const QByteArray &data) { QDataStream ds(data); QByteArray command; ds >> command; if (command == V8DEBUG) { QByteArray type; QByteArray response; ds >> type >> response; d->logReceiveMessage(_(V8DEBUG) + QLatin1Char(' ') + QLatin1String(type)); if (type == CONNECT) { //debugging session started } else if (type == INTERRUPT) { //debug break requested } else if (type == BREAKONSIGNAL) { //break on signal handler requested } else if (type == V8MESSAGE) { const QString responseString = QLatin1String(response); SDEBUG(responseString); d->logReceiveMessage(QLatin1String(V8MESSAGE) + QLatin1Char(' ') + responseString); const QVariantMap resp = d->parser.call(QScriptValue(), QScriptValueList() << QScriptValue(responseString)).toVariant().toMap(); const QString type(resp.value(_(TYPE)).toString()); if (type == _("response")) { bool success = resp.value(_("success")).toBool(); if (!success) { SDEBUG("Request was unsuccessful"); } const QString debugCommand(resp.value(_(COMMAND)).toString()); if (debugCommand == _(DISCONNECT)) { //debugging session ended } else if (debugCommand == _(CONTINEDEBUGGING)) { //do nothing, wait for next break } else if (debugCommand == _(BACKTRACE)) { if (success) { updateStack(resp.value(_(BODY)), resp.value(_(REFS))); } } else if (debugCommand == _(LOOKUP)) { if (success) { expandLocalsAndWatchers(resp.value(_(BODY)), resp.value(_(REFS))); } } else if (debugCommand == _(EVALUATE)) { int seq = resp.value(_("request_seq")).toInt(); if (success) { updateEvaluationResult(seq, success, resp.value(_(BODY)), resp.value(_(REFS))); } else { QVariantMap map; map.insert(_(TYPE), QVariant(_("string"))); map.insert(_(VALUE), resp.value(_("message"))); updateEvaluationResult(seq, success, QVariant(map), QVariant()); } } else if (debugCommand == _(LISTBREAKPOINTS)) { if (success) { updateBreakpoints(resp.value(_(BODY))); } } else if (debugCommand == _(SETBREAKPOINT)) { // { "seq" : , // "type" : "response", // "request_seq" : , // "command" : "setbreakpoint", // "body" : { "type" : <"function" or "script"> // "breakpoint" : // } // "running" : // "success" : true // } int seq = resp.value(_("request_seq")).toInt(); const QVariantMap breakpointData = resp.value(_(BODY)).toMap(); int index = breakpointData.value(_("breakpoint")).toInt(); if (d->breakpointsSync.contains(seq)) { BreakpointModelId id = d->breakpointsSync.take(seq); d->breakpoints.insert(id, index); //Is actual position info present? Then breakpoint was //accepted const QVariantList actualLocations = breakpointData.value( _("actual_locations")).toList(); if (actualLocations.count()) { //The breakpoint requested line should be same as //actual line BreakHandler *handler = d->engine->breakHandler(); if (handler->state(id) != BreakpointInserted) { BreakpointResponse br = handler->response(id); br.lineNumber = breakpointData.value(_("line") ).toInt() + 1; handler->setResponse(id, br); handler->notifyBreakpointInsertOk(id); } } } else { d->breakpointsTemp.append(index); } } else if (debugCommand == _(CHANGEBREAKPOINT)) { // DO NOTHING } else if (debugCommand == _(CLEARBREAKPOINT)) { // DO NOTHING } else if (debugCommand == _(SETEXCEPTIONBREAK)) { // { "seq" : , // "type" : "response", // "request_seq" : , // "command" : "setexceptionbreak", // "body" : { "type" : , // "enabled" : // } // "running" : true // "success" : true // } } else if (debugCommand == _(FRAME)) { if (success) { setCurrentFrameDetails(resp.value(_(BODY)), resp.value(_(REFS))); } } else if (debugCommand == _(SCOPE)) { if (success) { updateScope(resp.value(_(BODY)), resp.value(_(REFS))); } } else if (debugCommand == _(SCOPES)) { } else if (debugCommand == _(SOURCE)) { } else if (debugCommand == _(SCRIPTS)) { // { "seq" : , // "type" : "response", // "request_seq" : , // "command" : "scripts", // "body" : [ { "name" : , // "id" : // "lineOffset" : // "columnOffset" : // "lineCount" : // "data" : // "source" : // "sourceStart" : // "sourceLength" : // "scriptType" :