1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qv4debugclient_p.h"
5#include "qv4debugclient_p_p.h"
6#include "qqmldebugconnection_p.h"
7
8#include <private/qpacket_p.h>
9
10#include <QJsonDocument>
11#include <QJsonObject>
12#include <QJsonValue>
13#include <QJsonArray>
14
15QT_BEGIN_NAMESPACE
16
17const char *V8REQUEST = "v8request";
18const char *V8MESSAGE = "v8message";
19const char *SEQ = "seq";
20const char *TYPE = "type";
21const char *COMMAND = "command";
22const char *ARGUMENTS = "arguments";
23const char *STEPACTION = "stepaction";
24const char *STEPCOUNT = "stepcount";
25const char *EXPRESSION = "expression";
26const char *FRAME = "frame";
27const char *CONTEXT = "context";
28const char *GLOBAL = "global";
29const char *DISABLEBREAK = "disable_break";
30const char *HANDLES = "handles";
31const char *INCLUDESOURCE = "includeSource";
32const char *FROMFRAME = "fromFrame";
33const char *TOFRAME = "toFrame";
34const char *BOTTOM = "bottom";
35const char *NUMBER = "number";
36const char *FRAMENUMBER = "frameNumber";
37const char *TYPES = "types";
38const char *IDS = "ids";
39const char *FILTER = "filter";
40const char *FROMLINE = "fromLine";
41const char *TOLINE = "toLine";
42const char *TARGET = "target";
43const char *LINE = "line";
44const char *COLUMN = "column";
45const char *ENABLED = "enabled";
46const char *CONDITION = "condition";
47const char *IGNORECOUNT = "ignoreCount";
48const char *BREAKPOINT = "breakpoint";
49const char *FLAGS = "flags";
50
51const char *CONTINEDEBUGGING = "continue";
52const char *EVALUATE = "evaluate";
53const char *LOOKUP = "lookup";
54const char *BACKTRACE = "backtrace";
55const char *SCOPE = "scope";
56const char *SCOPES = "scopes";
57const char *SCRIPTS = "scripts";
58const char *SOURCE = "source";
59const char *SETBREAKPOINT = "setbreakpoint";
60const char *CLEARBREAKPOINT = "clearbreakpoint";
61const char *CHANGEBREAKPOINT = "changebreakpoint";
62const char *SETEXCEPTIONBREAK = "setexceptionbreak";
63const char *VERSION = "version";
64const char *DISCONNECT = "disconnect";
65const char *GARBAGECOLLECTOR = "gc";
66
67const char *CONNECT = "connect";
68const char *INTERRUPT = "interrupt";
69
70const char *REQUEST = "request";
71const char *IN = "in";
72const char *NEXT = "next";
73const char *OUT = "out";
74
75const char *SCRIPT = "script";
76const char *SCRIPTREGEXP = "scriptRegExp";
77const char *EVENT = "event";
78
79const char *ALL = "all";
80const char *UNCAUGHT = "uncaught";
81
82#define VARIANTMAPINIT \
83 Q_D(QV4DebugClient); \
84 QJsonObject jsonVal; \
85 jsonVal.insert(QLatin1String(SEQ), d->seq++); \
86 jsonVal.insert(QLatin1String(TYPE), QLatin1String(REQUEST));
87
88QV4DebugClient::QV4DebugClient(QQmlDebugConnection *connection)
89 : QQmlDebugClient(*new QV4DebugClientPrivate(connection))
90{
91 QObject::connect(sender: this, signal: &QQmlDebugClient::stateChanged,
92 context: this, slot: [this](State state) { d_func()->onStateChanged(state); });
93}
94
95QV4DebugClientPrivate::QV4DebugClientPrivate(QQmlDebugConnection *connection) :
96 QQmlDebugClientPrivate(QLatin1String("V8Debugger"), connection)
97{
98}
99
100void QV4DebugClient::connect()
101{
102 Q_D(QV4DebugClient);
103 d->sendMessage(command: CONNECT);
104}
105
106void QV4DebugClient::interrupt()
107{
108 Q_D(QV4DebugClient);
109 d->sendMessage(command: INTERRUPT);
110}
111
112void QV4DebugClient::continueDebugging(StepAction action)
113{
114 // { "seq" : <number>,
115 // "type" : "request",
116 // "command" : "continue",
117 // "arguments" : { "stepaction" : <"in", "next" or "out">,
118 // "stepcount" : <number of steps (default 1)>
119 // }
120 // }
121 VARIANTMAPINIT;
122 jsonVal.insert(key: QLatin1String(COMMAND), value: QLatin1String(CONTINEDEBUGGING));
123
124 if (action != Continue) {
125 QJsonObject args;
126 switch (action) {
127 case In:
128 args.insert(key: QLatin1String(STEPACTION), value: QLatin1String(IN));
129 break;
130 case Out:
131 args.insert(key: QLatin1String(STEPACTION), value: QLatin1String(OUT));
132 break;
133 case Next:
134 args.insert(key: QLatin1String(STEPACTION), value: QLatin1String(NEXT));
135 break;
136 default:
137 break;
138 }
139 jsonVal.insert(key: QLatin1String(ARGUMENTS), value: args);
140 }
141
142 d->sendMessage(command: V8REQUEST, args: jsonVal);
143}
144
145void QV4DebugClient::evaluate(const QString &expr, int frame, int context)
146{
147 // { "seq" : <number>,
148 // "type" : "request",
149 // "command" : "evaluate",
150 // "arguments" : { "expression" : <expression to evaluate>,
151 // "frame" : <number>,
152 // "context" : <object ID>
153 // }
154 // }
155 VARIANTMAPINIT;
156 jsonVal.insert(key: QLatin1String(COMMAND), value: QLatin1String(EVALUATE));
157
158 QJsonObject args;
159 args.insert(key: QLatin1String(EXPRESSION), value: expr);
160
161 if (frame != -1)
162 args.insert(key: QLatin1String(FRAME), value: frame);
163
164 if (context != -1)
165 args.insert(key: QLatin1String(CONTEXT), value: context);
166
167 jsonVal.insert(key: QLatin1String(ARGUMENTS), value: args);
168
169 d->sendMessage(command: V8REQUEST, args: jsonVal);
170}
171
172void QV4DebugClient::lookup(const QList<int> &handles, bool includeSource)
173{
174 // { "seq" : <number>,
175 // "type" : "request",
176 // "command" : "lookup",
177 // "arguments" : { "handles" : <array of handles>,
178 // "includeSource" : <boolean indicating whether the source will be included when script objects are returned>,
179 // }
180 // }
181 VARIANTMAPINIT;
182 jsonVal.insert(key: QLatin1String(COMMAND),value: (QLatin1String(LOOKUP)));
183
184 QJsonObject args;
185 QJsonArray array;
186
187 for (int handle : handles)
188 array.append(value: handle);
189
190 args.insert(key: QLatin1String(HANDLES), value: array);
191
192 if (includeSource)
193 args.insert(key: QLatin1String(INCLUDESOURCE), value: includeSource);
194
195 jsonVal.insert(key: QLatin1String(ARGUMENTS), value: args);
196
197 d->sendMessage(command: V8REQUEST, args: jsonVal);
198}
199
200void QV4DebugClient::backtrace(int fromFrame, int toFrame, bool bottom)
201{
202 // { "seq" : <number>,
203 // "type" : "request",
204 // "command" : "backtrace",
205 // "arguments" : { "fromFrame" : <number>
206 // "toFrame" : <number>
207 // "bottom" : <boolean, set to true if the bottom of the stack is requested>
208 // }
209 // }
210 VARIANTMAPINIT;
211 jsonVal.insert(key: QLatin1String(COMMAND), value: QLatin1String(BACKTRACE));
212
213 QJsonObject args;
214
215 if (fromFrame != -1)
216 args.insert(key: QLatin1String(FROMFRAME), value: fromFrame);
217
218 if (toFrame != -1)
219 args.insert(key: QLatin1String(TOFRAME), value: toFrame);
220
221 if (bottom)
222 args.insert(key: QLatin1String(BOTTOM), value: bottom);
223
224 jsonVal.insert(key: QLatin1String(ARGUMENTS), value: args);
225 d->sendMessage(command: V8REQUEST, args: jsonVal);
226}
227
228void QV4DebugClient::frame(int number)
229{
230 // { "seq" : <number>,
231 // "type" : "request",
232 // "command" : "frame",
233 // "arguments" : { "number" : <frame number>
234 // }
235 // }
236 VARIANTMAPINIT;
237 jsonVal.insert(key: QLatin1String(COMMAND), value: QLatin1String(FRAME));
238
239 if (number != -1) {
240 QJsonObject args;
241 args.insert(key: QLatin1String(NUMBER), value: number);
242 jsonVal.insert(key: QLatin1String(ARGUMENTS), value: args);
243 }
244
245 d->sendMessage(command: V8REQUEST, args: jsonVal);
246}
247
248void QV4DebugClient::scope(int number, int frameNumber)
249{
250 // { "seq" : <number>,
251 // "type" : "request",
252 // "command" : "scope",
253 // "arguments" : { "number" : <scope number>
254 // "frameNumber" : <frame number, optional uses selected frame if missing>
255 // }
256 // }
257 VARIANTMAPINIT;
258 jsonVal.insert(key: QLatin1String(COMMAND), value: QLatin1String(SCOPE));
259
260 if (number != -1) {
261 QJsonObject args;
262 args.insert(key: QLatin1String(NUMBER), value: number);
263
264 if (frameNumber != -1)
265 args.insert(key: QLatin1String(FRAMENUMBER), value: frameNumber);
266
267 jsonVal.insert(key: QLatin1String(ARGUMENTS), value: args);
268 }
269
270 d->sendMessage(command: V8REQUEST, args: jsonVal);
271}
272
273void QV4DebugClient::scripts(int types, const QList<int> &ids, bool includeSource)
274{
275 // { "seq" : <number>,
276 // "type" : "request",
277 // "command" : "scripts",
278 // "arguments" : { "types" : <types of scripts to retrieve
279 // set bit 0 for native scripts
280 // set bit 1 for extension scripts
281 // set bit 2 for normal scripts
282 // (default is 4 for normal scripts)>
283 // "ids" : <array of id's of scripts to return. If this is not specified all scripts are requrned>
284 // "includeSource" : <boolean indicating whether the source code should be included for the scripts returned>
285 // "filter" : <string or number: filter string or script id.
286 // If a number is specified, then only the script with the same number as its script id will be retrieved.
287 // If a string is specified, then only scripts whose names contain the filter string will be retrieved.>
288 // }
289 // }
290 VARIANTMAPINIT;
291 jsonVal.insert(key: QLatin1String(COMMAND), value: QLatin1String(SCRIPTS));
292
293 QJsonObject args;
294 args.insert(key: QLatin1String(TYPES), value: types);
295
296 if (ids.size()) {
297 QJsonArray array;
298 for (int id : ids)
299 array.append(value: id);
300
301 args.insert(key: QLatin1String(IDS), value: array);
302 }
303
304 if (includeSource)
305 args.insert(key: QLatin1String(INCLUDESOURCE), value: includeSource);
306
307 jsonVal.insert(key: QLatin1String(ARGUMENTS), value: args);
308 d->sendMessage(command: V8REQUEST, args: jsonVal);
309}
310
311void QV4DebugClient::setBreakpoint(const QString &target, int line, int column, bool enabled,
312 const QString &condition, int ignoreCount)
313{
314 // { "seq" : <number>,
315 // "type" : "request",
316 // "command" : "setbreakpoint",
317 // "arguments" : { "type" : "scriptRegExp"
318 // "target" : <function expression or script identification>
319 // "line" : <line in script or function>
320 // "column" : <character position within the line>
321 // "enabled" : <initial enabled state. True or false, default is true>
322 // "condition" : <string with break point condition>
323 // "ignoreCount" : <number specifying the number of break point hits to ignore, default value is 0>
324 // }
325 // }
326
327 VARIANTMAPINIT;
328 jsonVal.insert(key: QLatin1String(COMMAND), value: QLatin1String(SETBREAKPOINT));
329
330 QJsonObject args;
331
332 args.insert(key: QLatin1String(TYPE), value: QLatin1String(SCRIPTREGEXP));
333 args.insert(key: QLatin1String(TARGET), value: target);
334
335 if (line != -1)
336 args.insert(key: QLatin1String(LINE), value: line);
337
338 if (column != -1)
339 args.insert(key: QLatin1String(COLUMN), value: column);
340
341 args.insert(key: QLatin1String(ENABLED), value: enabled);
342
343 if (!condition.isEmpty())
344 args.insert(key: QLatin1String(CONDITION), value: condition);
345
346 if (ignoreCount != -1)
347 args.insert(key: QLatin1String(IGNORECOUNT), value: ignoreCount);
348
349 jsonVal.insert(key: QLatin1String(ARGUMENTS),value: args);
350 d->sendMessage(command: V8REQUEST, args: jsonVal);
351}
352
353void QV4DebugClient::clearBreakpoint(int breakpoint)
354{
355 // { "seq" : <number>,
356 // "type" : "request",
357 // "command" : "clearbreakpoint",
358 // "arguments" : { "breakpoint" : <number of the break point to clear>
359 // }
360 // }
361 VARIANTMAPINIT;
362 jsonVal.insert(key: QLatin1String(COMMAND), value: QLatin1String(CLEARBREAKPOINT));
363
364 QJsonObject args;
365 args.insert(key: QLatin1String(BREAKPOINT), value: breakpoint);
366 jsonVal.insert(key: QLatin1String(ARGUMENTS),value: args);
367
368 d->sendMessage(command: V8REQUEST, args: jsonVal);
369}
370
371void QV4DebugClient::changeBreakpoint(int breakpoint, bool enabled)
372{
373 // { "seq" : <number>,
374 // "type" : "request",
375 // "command" : "changebreakpoint",
376 // "arguments" : { "breakpoint" : <number of the break point to change>
377 // "enabled" : <bool: enables the break type if true, disables if false>
378 // }
379 // }
380 VARIANTMAPINIT;
381 jsonVal.insert(key: QLatin1String(COMMAND), value: QLatin1String(CHANGEBREAKPOINT));
382
383 QJsonObject args;
384 args.insert(key: QLatin1String(BREAKPOINT), value: breakpoint);
385 args.insert(key: QLatin1String(ENABLED), value: enabled);
386
387 jsonVal.insert(key: QLatin1String(ARGUMENTS), value: args);
388 d->sendMessage(command: V8REQUEST, args: jsonVal);
389}
390
391void QV4DebugClient::setExceptionBreak(Exception type, bool enabled)
392{
393 // { "seq" : <number>,
394 // "type" : "request",
395 // "command" : "setexceptionbreak",
396 // "arguments" : { "type" : <string: "all", or "uncaught">,
397 // "enabled" : <optional bool: enables the break type if true>
398 // }
399 // }
400 VARIANTMAPINIT;
401 jsonVal.insert(key: QLatin1String(COMMAND), value: QLatin1String(SETEXCEPTIONBREAK));
402
403 QJsonObject args;
404
405 if (type == All)
406 args.insert(key: QLatin1String(TYPE), value: QLatin1String(ALL));
407 else if (type == Uncaught)
408 args.insert(key: QLatin1String(TYPE), value: QLatin1String(UNCAUGHT));
409
410 if (enabled)
411 args.insert(key: QLatin1String(ENABLED), value: enabled);
412
413 jsonVal.insert(key: QLatin1String(ARGUMENTS), value: args);
414 d->sendMessage(command: V8REQUEST, args: jsonVal);
415}
416
417void QV4DebugClient::version()
418{
419 // { "seq" : <number>,
420 // "type" : "request",
421 // "command" : "version",
422 // }
423 VARIANTMAPINIT;
424 jsonVal.insert(key: QLatin1String(COMMAND), value: QLatin1String(VERSION));
425 d->sendMessage(command: V8REQUEST, args: jsonVal);
426}
427
428QV4DebugClient::Response QV4DebugClient::response() const
429{
430 Q_D(const QV4DebugClient);
431 const QJsonObject value = QJsonDocument::fromJson(json: d->response).object();
432 return {
433 .command: value.value(key: QLatin1String(COMMAND)).toString(),
434 .body: value.value(key: QLatin1String("body"))
435 };
436}
437
438void QV4DebugClient::disconnect()
439{
440 // { "seq" : <number>,
441 // "type" : "request",
442 // "command" : "disconnect",
443 // }
444 VARIANTMAPINIT;
445 jsonVal.insert(key: QLatin1String(COMMAND), value: QLatin1String(DISCONNECT));
446 d->sendMessage(command: DISCONNECT, args: jsonVal);
447}
448
449void QV4DebugClientPrivate::onStateChanged(QQmlDebugClient::State state)
450{
451 if (state == QQmlDebugClient::Enabled)
452 flushSendBuffer();
453}
454
455void QV4DebugClient::messageReceived(const QByteArray &data)
456{
457 Q_D(QV4DebugClient);
458 QPacket ds(connection()->currentDataStreamVersion(), data);
459 QByteArray command;
460 ds >> command;
461
462 if (command == "V8DEBUG") {
463 QByteArray type;
464 ds >> type >> d->response;
465
466 if (type == CONNECT) {
467 emit connected();
468
469 } else if (type == INTERRUPT) {
470 emit interrupted();
471
472 } else if (type == V8MESSAGE) {
473 const QJsonObject value = QJsonDocument::fromJson(json: d->response).object();
474 QString type = value.value(key: QLatin1String(TYPE)).toString();
475
476 if (type == QLatin1String("response")) {
477
478 if (!value.value(key: QLatin1String("success")).toBool()) {
479 emit failure();
480 qDebug() << "Received success == false response from application:"
481 << value.value(key: QLatin1String("message")).toString();
482 return;
483 }
484
485 QString debugCommand(value.value(key: QLatin1String(COMMAND)).toString());
486 if (debugCommand == QLatin1String(BACKTRACE) ||
487 debugCommand == QLatin1String(LOOKUP) ||
488 debugCommand == QLatin1String(SETBREAKPOINT) ||
489 debugCommand == QLatin1String(EVALUATE) ||
490 debugCommand == QLatin1String(VERSION) ||
491 debugCommand == QLatin1String(DISCONNECT) ||
492 debugCommand == QLatin1String(GARBAGECOLLECTOR) ||
493 debugCommand == QLatin1String(CHANGEBREAKPOINT) ||
494 debugCommand == QLatin1String(CLEARBREAKPOINT) ||
495 debugCommand == QLatin1String(FRAME) ||
496 debugCommand == QLatin1String(SCOPE) ||
497 debugCommand == QLatin1String(SCOPES) ||
498 debugCommand == QLatin1String(SCRIPTS) ||
499 debugCommand == QLatin1String(SOURCE) ||
500 debugCommand == QLatin1String(SETEXCEPTIONBREAK)) {
501 emit result();
502 } else {
503 // DO NOTHING
504 }
505
506 } else if (type == QLatin1String(EVENT)) {
507 QString event(value.value(key: QLatin1String(EVENT)).toString());
508
509 if (event == QLatin1String("break") || event == QLatin1String("exception"))
510 emit stopped();
511 }
512 }
513 }
514}
515
516void QV4DebugClientPrivate::sendMessage(const QByteArray &command, const QJsonObject &args)
517{
518 Q_Q(QV4DebugClient);
519 const QByteArray msg = packMessage(type: command, object: args);
520 if (q->state() == QQmlDebugClient::Enabled) {
521 q->sendMessage(message: msg);
522 } else {
523 sendBuffer.append(t: msg);
524 }
525}
526
527void QV4DebugClientPrivate::flushSendBuffer()
528{
529 foreach (const QByteArray &msg, sendBuffer)
530 sendMessage(command: msg);
531 sendBuffer.clear();
532}
533
534QByteArray QV4DebugClientPrivate::packMessage(const QByteArray &type, const QJsonObject &object)
535{
536 QPacket rs(connection->currentDataStreamVersion());
537 QByteArray cmd = "V8DEBUG";
538 rs << cmd << type << QJsonDocument(object).toJson(format: QJsonDocument::Compact);
539 return rs.data();
540}
541
542QT_END_NAMESPACE
543
544#include "moc_qv4debugclient_p.cpp"
545

source code of qtdeclarative/src/qmldebug/qv4debugclient.cpp