blob: 76c2f1ebea176b64178084c82574a3fc4d8ab2f0 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#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.count()) {
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()
{
foreach (const QByteArray &msg, 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