// Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qv4debugclient_p.h" #include "qv4debugclient_p_p.h" #include "qqmldebugconnection_p.h" #include #include #include #include #include QT_BEGIN_NAMESPACE const char *V8REQUEST = "v8request"; const char *V8MESSAGE = "v8message"; const char *SEQ = "seq"; const char *TYPE = "type"; const char *COMMAND = "command"; const char *ARGUMENTS = "arguments"; const char *STEPACTION = "stepaction"; const char *STEPCOUNT = "stepcount"; const char *EXPRESSION = "expression"; const char *FRAME = "frame"; const char *CONTEXT = "context"; const char *GLOBAL = "global"; const char *DISABLEBREAK = "disable_break"; const char *HANDLES = "handles"; const char *INCLUDESOURCE = "includeSource"; const char *FROMFRAME = "fromFrame"; const char *TOFRAME = "toFrame"; const char *BOTTOM = "bottom"; const char *NUMBER = "number"; const char *FRAMENUMBER = "frameNumber"; const char *TYPES = "types"; const char *IDS = "ids"; const char *FILTER = "filter"; const char *FROMLINE = "fromLine"; const char *TOLINE = "toLine"; const char *TARGET = "target"; const char *LINE = "line"; const char *COLUMN = "column"; const char *ENABLED = "enabled"; const char *CONDITION = "condition"; const char *IGNORECOUNT = "ignoreCount"; const char *BREAKPOINT = "breakpoint"; const char *FLAGS = "flags"; const char *CONTINEDEBUGGING = "continue"; const char *EVALUATE = "evaluate"; const char *LOOKUP = "lookup"; const char *BACKTRACE = "backtrace"; const char *SCOPE = "scope"; const char *SCOPES = "scopes"; const char *SCRIPTS = "scripts"; const char *SOURCE = "source"; const char *SETBREAKPOINT = "setbreakpoint"; const char *CLEARBREAKPOINT = "clearbreakpoint"; const char *CHANGEBREAKPOINT = "changebreakpoint"; const char *SETEXCEPTIONBREAK = "setexceptionbreak"; const char *VERSION = "version"; const char *DISCONNECT = "disconnect"; const char *GARBAGECOLLECTOR = "gc"; const char *CONNECT = "connect"; const char *INTERRUPT = "interrupt"; const char *REQUEST = "request"; const char *IN = "in"; const char *NEXT = "next"; const char *OUT = "out"; const char *SCRIPT = "script"; const char *SCRIPTREGEXP = "scriptRegExp"; const char *EVENT = "event"; const char *ALL = "all"; const char *UNCAUGHT = "uncaught"; #define VARIANTMAPINIT \ Q_D(QV4DebugClient); \ QJsonObject jsonVal; \ jsonVal.insert(QLatin1String(SEQ), d->seq++); \ jsonVal.insert(QLatin1String(TYPE), QLatin1String(REQUEST)); QV4DebugClient::QV4DebugClient(QQmlDebugConnection *connection) : QQmlDebugClient(*new QV4DebugClientPrivate(connection)) { QObject::connect(this, &QQmlDebugClient::stateChanged, this, [this](State state) { d_func()->onStateChanged(state); }); } QV4DebugClientPrivate::QV4DebugClientPrivate(QQmlDebugConnection *connection) : QQmlDebugClientPrivate(QLatin1String("V8Debugger"), connection) { } void QV4DebugClient::connect() { Q_D(QV4DebugClient); d->sendMessage(CONNECT); } void QV4DebugClient::interrupt() { Q_D(QV4DebugClient); d->sendMessage(INTERRUPT); } void QV4DebugClient::continueDebugging(StepAction action) { // { "seq" : , // "type" : "request", // "command" : "continue", // "arguments" : { "stepaction" : <"in", "next" or "out">, // "stepcount" : // } // } VARIANTMAPINIT; jsonVal.insert(QLatin1String(COMMAND), QLatin1String(CONTINEDEBUGGING)); if (action != Continue) { QJsonObject args; switch (action) { case In: args.insert(QLatin1String(STEPACTION), QLatin1String(IN)); break; case Out: args.insert(QLatin1String(STEPACTION), QLatin1String(OUT)); break; case Next: args.insert(QLatin1String(STEPACTION), QLatin1String(NEXT)); break; default: break; } jsonVal.insert(QLatin1String(ARGUMENTS), args); } d->sendMessage(V8REQUEST, jsonVal); } void QV4DebugClient::evaluate(const QString &expr, int frame, int context) { // { "seq" : , // "type" : "request", // "command" : "evaluate", // "arguments" : { "expression" : , // "frame" : , // "context" : // } // } VARIANTMAPINIT; jsonVal.insert(QLatin1String(COMMAND), QLatin1String(EVALUATE)); QJsonObject args; args.insert(QLatin1String(EXPRESSION), expr); if (frame != -1) args.insert(QLatin1String(FRAME), frame); if (context != -1) args.insert(QLatin1String(CONTEXT), context); jsonVal.insert(QLatin1String(ARGUMENTS), args); d->sendMessage(V8REQUEST, jsonVal); } void QV4DebugClient::lookup(const QList &handles, bool includeSource) { // { "seq" : , // "type" : "request", // "command" : "lookup", // "arguments" : { "handles" : , // "includeSource" : , // } // } VARIANTMAPINIT; jsonVal.insert(QLatin1String(COMMAND),(QLatin1String(LOOKUP))); QJsonObject args; QJsonArray array; for (int handle : handles) array.append(handle); args.insert(QLatin1String(HANDLES), array); if (includeSource) args.insert(QLatin1String(INCLUDESOURCE), includeSource); jsonVal.insert(QLatin1String(ARGUMENTS), args); d->sendMessage(V8REQUEST, jsonVal); } void QV4DebugClient::backtrace(int fromFrame, int toFrame, bool bottom) { // { "seq" : , // "type" : "request", // "command" : "backtrace", // "arguments" : { "fromFrame" : // "toFrame" : // "bottom" : // } // } VARIANTMAPINIT; jsonVal.insert(QLatin1String(COMMAND), QLatin1String(BACKTRACE)); QJsonObject args; if (fromFrame != -1) args.insert(QLatin1String(FROMFRAME), fromFrame); if (toFrame != -1) args.insert(QLatin1String(TOFRAME), toFrame); if (bottom) args.insert(QLatin1String(BOTTOM), bottom); jsonVal.insert(QLatin1String(ARGUMENTS), args); d->sendMessage(V8REQUEST, jsonVal); } void QV4DebugClient::frame(int number) { // { "seq" : , // "type" : "request", // "command" : "frame", // "arguments" : { "number" : // } // } VARIANTMAPINIT; jsonVal.insert(QLatin1String(COMMAND), QLatin1String(FRAME)); if (number != -1) { QJsonObject args; args.insert(QLatin1String(NUMBER), number); jsonVal.insert(QLatin1String(ARGUMENTS), args); } d->sendMessage(V8REQUEST, jsonVal); } void QV4DebugClient::scope(int number, int frameNumber) { // { "seq" : , // "type" : "request", // "command" : "scope", // "arguments" : { "number" : // "frameNumber" : // } // } VARIANTMAPINIT; jsonVal.insert(QLatin1String(COMMAND), QLatin1String(SCOPE)); if (number != -1) { QJsonObject args; args.insert(QLatin1String(NUMBER), number); if (frameNumber != -1) args.insert(QLatin1String(FRAMENUMBER), frameNumber); jsonVal.insert(QLatin1String(ARGUMENTS), args); } d->sendMessage(V8REQUEST, jsonVal); } void QV4DebugClient::scripts(int types, const QList &ids, bool includeSource) { // { "seq" : , // "type" : "request", // "command" : "scripts", // "arguments" : { "types" : // "ids" : // "includeSource" : // "filter" : // } // } VARIANTMAPINIT; jsonVal.insert(QLatin1String(COMMAND), QLatin1String(SCRIPTS)); QJsonObject args; args.insert(QLatin1String(TYPES), types); if (ids.size()) { QJsonArray array; for (int id : ids) array.append(id); args.insert(QLatin1String(IDS), array); } if (includeSource) args.insert(QLatin1String(INCLUDESOURCE), includeSource); jsonVal.insert(QLatin1String(ARGUMENTS), args); d->sendMessage(V8REQUEST, jsonVal); } void QV4DebugClient::setBreakpoint(const QString &target, int line, int column, bool enabled, const QString &condition, int ignoreCount) { // { "seq" : , // "type" : "request", // "command" : "setbreakpoint", // "arguments" : { "type" : "scriptRegExp" // "target" : // "line" : // "column" : // "enabled" : // "condition" : // "ignoreCount" : // } // } VARIANTMAPINIT; jsonVal.insert(QLatin1String(COMMAND), QLatin1String(SETBREAKPOINT)); QJsonObject args; args.insert(QLatin1String(TYPE), QLatin1String(SCRIPTREGEXP)); args.insert(QLatin1String(TARGET), target); if (line != -1) args.insert(QLatin1String(LINE), line); if (column != -1) args.insert(QLatin1String(COLUMN), column); args.insert(QLatin1String(ENABLED), enabled); if (!condition.isEmpty()) args.insert(QLatin1String(CONDITION), condition); if (ignoreCount != -1) args.insert(QLatin1String(IGNORECOUNT), ignoreCount); jsonVal.insert(QLatin1String(ARGUMENTS),args); d->sendMessage(V8REQUEST, jsonVal); } void QV4DebugClient::clearBreakpoint(int breakpoint) { // { "seq" : , // "type" : "request", // "command" : "clearbreakpoint", // "arguments" : { "breakpoint" : // } // } VARIANTMAPINIT; jsonVal.insert(QLatin1String(COMMAND), QLatin1String(CLEARBREAKPOINT)); QJsonObject args; args.insert(QLatin1String(BREAKPOINT), breakpoint); jsonVal.insert(QLatin1String(ARGUMENTS),args); d->sendMessage(V8REQUEST, jsonVal); } void QV4DebugClient::changeBreakpoint(int breakpoint, bool enabled) { // { "seq" : , // "type" : "request", // "command" : "changebreakpoint", // "arguments" : { "breakpoint" : // "enabled" : // } // } VARIANTMAPINIT; jsonVal.insert(QLatin1String(COMMAND), QLatin1String(CHANGEBREAKPOINT)); QJsonObject args; args.insert(QLatin1String(BREAKPOINT), breakpoint); args.insert(QLatin1String(ENABLED), enabled); jsonVal.insert(QLatin1String(ARGUMENTS), args); d->sendMessage(V8REQUEST, jsonVal); } void QV4DebugClient::setExceptionBreak(Exception type, bool enabled) { // { "seq" : , // "type" : "request", // "command" : "setexceptionbreak", // "arguments" : { "type" : , // "enabled" : // } // } VARIANTMAPINIT; jsonVal.insert(QLatin1String(COMMAND), QLatin1String(SETEXCEPTIONBREAK)); QJsonObject args; if (type == All) args.insert(QLatin1String(TYPE), QLatin1String(ALL)); else if (type == Uncaught) args.insert(QLatin1String(TYPE), QLatin1String(UNCAUGHT)); if (enabled) args.insert(QLatin1String(ENABLED), enabled); jsonVal.insert(QLatin1String(ARGUMENTS), args); d->sendMessage(V8REQUEST, jsonVal); } void QV4DebugClient::version() { // { "seq" : , // "type" : "request", // "command" : "version", // } VARIANTMAPINIT; jsonVal.insert(QLatin1String(COMMAND), QLatin1String(VERSION)); d->sendMessage(V8REQUEST, jsonVal); } QV4DebugClient::Response QV4DebugClient::response() const { Q_D(const QV4DebugClient); const QJsonObject value = QJsonDocument::fromJson(d->response).object(); return { value.value(QLatin1String(COMMAND)).toString(), value.value(QLatin1String("body")) }; } void QV4DebugClient::disconnect() { // { "seq" : , // "type" : "request", // "command" : "disconnect", // } VARIANTMAPINIT; jsonVal.insert(QLatin1String(COMMAND), QLatin1String(DISCONNECT)); d->sendMessage(DISCONNECT, jsonVal); } void QV4DebugClientPrivate::onStateChanged(QQmlDebugClient::State state) { if (state == QQmlDebugClient::Enabled) flushSendBuffer(); } void QV4DebugClient::messageReceived(const QByteArray &data) { Q_D(QV4DebugClient); QPacket ds(connection()->currentDataStreamVersion(), data); QByteArray command; ds >> command; if (command == "V8DEBUG") { QByteArray type; ds >> type >> d->response; if (type == CONNECT) { emit connected(); } else if (type == INTERRUPT) { emit interrupted(); } else if (type == V8MESSAGE) { const QJsonObject value = QJsonDocument::fromJson(d->response).object(); QString type = value.value(QLatin1String(TYPE)).toString(); if (type == QLatin1String("response")) { if (!value.value(QLatin1String("success")).toBool()) { emit failure(); qDebug() << "Received success == false response from application:" << value.value(QLatin1String("message")).toString(); return; } QString debugCommand(value.value(QLatin1String(COMMAND)).toString()); if (debugCommand == QLatin1String(BACKTRACE) || debugCommand == QLatin1String(LOOKUP) || debugCommand == QLatin1String(SETBREAKPOINT) || debugCommand == QLatin1String(EVALUATE) || debugCommand == QLatin1String(VERSION) || debugCommand == QLatin1String(DISCONNECT) || debugCommand == QLatin1String(GARBAGECOLLECTOR) || debugCommand == QLatin1String(CHANGEBREAKPOINT) || debugCommand == QLatin1String(CLEARBREAKPOINT) || debugCommand == QLatin1String(FRAME) || debugCommand == QLatin1String(SCOPE) || debugCommand == QLatin1String(SCOPES) || debugCommand == QLatin1String(SCRIPTS) || debugCommand == QLatin1String(SOURCE) || debugCommand == QLatin1String(SETEXCEPTIONBREAK)) { emit result(); } else { // DO NOTHING } } else if (type == QLatin1String(EVENT)) { QString event(value.value(QLatin1String(EVENT)).toString()); if (event == QLatin1String("break") || event == QLatin1String("exception")) emit stopped(); } } } } void QV4DebugClientPrivate::sendMessage(const QByteArray &command, const QJsonObject &args) { Q_Q(QV4DebugClient); const QByteArray msg = packMessage(command, args); if (q->state() == QQmlDebugClient::Enabled) { q->sendMessage(msg); } else { sendBuffer.append(msg); } } void QV4DebugClientPrivate::flushSendBuffer() { for (const QByteArray &msg : std::as_const(sendBuffer)) sendMessage(msg); sendBuffer.clear(); } QByteArray QV4DebugClientPrivate::packMessage(const QByteArray &type, const QJsonObject &object) { QPacket rs(connection->currentDataStreamVersion()); QByteArray cmd = "V8DEBUG"; rs << cmd << type << QJsonDocument(object).toJson(QJsonDocument::Compact); return rs.data(); } QT_END_NAMESPACE #include "moc_qv4debugclient_p.cpp"