Revision control
++ // 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
++ // Qt-Security score:significant
++
++ #include "qv4debugclient_p.h"
++ #include "qv4debugclient_p_p.h"
++ #include "qqmldebugconnection_p.h"
++
++ #include <private/qpacket_p.h>
++
++ #include <QJsonDocument>
++ #include <QJsonObject>
++ #include <QJsonValue>
++ #include <QJsonArray>
++
++ 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" : <number>,
++ // "type" : "request",
++ // "command" : "continue",
++ // "arguments" : { "stepaction" : <"in", "next" or "out">,
++ // "stepcount" : <number of steps (default 1)>
++ // }
++ // }
++ 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" : <number>,
++ // "type" : "request",
++ // "command" : "evaluate",
++ // "arguments" : { "expression" : <expression to evaluate>,
++ // "frame" : <number>,
++ // "context" : <object ID>
++ // }
++ // }
++ 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<int> &handles, bool includeSource)
++ {
++ // { "seq" : <number>,
++ // "type" : "request",
++ // "command" : "lookup",
++ // "arguments" : { "handles" : <array of handles>,
++ // "includeSource" : <boolean indicating whether the source will be included when script objects are returned>,
++ // }
++ // }
++ 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" : <number>,
++ // "type" : "request",
++ // "command" : "backtrace",
++ // "arguments" : { "fromFrame" : <number>
++ // "toFrame" : <number>
++ // "bottom" : <boolean, set to true if the bottom of the stack is requested>
++ // }
++ // }
++ 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" : <number>,
++ // "type" : "request",
++ // "command" : "frame",
++ // "arguments" : { "number" : <frame 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" : <number>,
++ // "type" : "request",
++ // "command" : "scope",
++ // "arguments" : { "number" : <scope number>
++ // "frameNumber" : <frame number, optional uses selected frame if missing>
++ // }
++ // }
++ 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<int> &ids, bool includeSource)
++ {
++ // { "seq" : <number>,
++ // "type" : "request",
++ // "command" : "scripts",
++ // "arguments" : { "types" : <types of scripts to retrieve
++ // set bit 0 for native scripts
++ // set bit 1 for extension scripts
++ // set bit 2 for normal scripts
++ // (default is 4 for normal scripts)>
++ // "ids" : <array of id's of scripts to return. If this is not specified all scripts are requrned>
++ // "includeSource" : <boolean indicating whether the source code should be included for the scripts returned>
++ // "filter" : <string or number: filter string or script id.
++ // If a number is specified, then only the script with the same number as its script id will be retrieved.
++ // If a string is specified, then only scripts whose names contain the filter string will be retrieved.>
++ // }
++ // }
++ 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" : <number>,
++ // "type" : "request",
++ // "command" : "setbreakpoint",
++ // "arguments" : { "type" : "scriptRegExp"
++ // "target" : <function expression or script identification>
++ // "line" : <line in script or function>
++ // "column" : <character position within the line>
++ // "enabled" : <initial enabled state. True or false, default is true>
++ // "condition" : <string with break point condition>
++ // "ignoreCount" : <number specifying the number of break point hits to ignore, default value is 0>
++ // }
++ // }
++
++ 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" : <number>,
++ // "type" : "request",
++ // "command" : "clearbreakpoint",
++ // "arguments" : { "breakpoint" : <number of the break point to clear>
++ // }
++ // }
++ 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" : <number>,
++ // "type" : "request",
++ // "command" : "changebreakpoint",
++ // "arguments" : { "breakpoint" : <number of the break point to change>
++ // "enabled" : <bool: enables the break type if true, disables if false>
++ // }
++ // }
++ 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" : <number>,
++ // "type" : "request",
++ // "command" : "setexceptionbreak",
++ // "arguments" : { "type" : <string: "all", or "uncaught">,
++ // "enabled" : <optional bool: enables the break type if true>
++ // }
++ // }
++ 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" : <number>,
++ // "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" : <number>,
++ // "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"