1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQml module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qv4debugservice.h" |
41 | #include "qv4debugjob.h" |
42 | #include "qqmlengine.h" |
43 | |
44 | #include <private/qv4engine_p.h> |
45 | #include <private/qv4function_p.h> |
46 | #include <private/qqmldebugconnector_p.h> |
47 | #include <private/qversionedpacket_p.h> |
48 | |
49 | #include <QtCore/QJsonArray> |
50 | #include <QtCore/QJsonDocument> |
51 | #include <QtCore/QJsonObject> |
52 | #include <QtCore/QJsonValue> |
53 | |
54 | const char *const V4_CONNECT = "connect" ; |
55 | const char *const V4_DISCONNECT = "disconnect" ; |
56 | const char *const V4_BREAK_ON_SIGNAL = "breakonsignal" ; |
57 | const char *const V4_PAUSE = "interrupt" ; |
58 | |
59 | #define NO_PROTOCOL_TRACING |
60 | #ifdef NO_PROTOCOL_TRACING |
61 | # define TRACE_PROTOCOL(x) |
62 | #else |
63 | #include <QtCore/QDebug> |
64 | # define TRACE_PROTOCOL(x) x |
65 | #endif |
66 | |
67 | QT_BEGIN_NAMESPACE |
68 | |
69 | class V4CommandHandler; |
70 | class UnknownV4CommandHandler; |
71 | |
72 | using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>; |
73 | |
74 | int QV4DebugServiceImpl::sequence = 0; |
75 | |
76 | class V4CommandHandler |
77 | { |
78 | public: |
79 | V4CommandHandler(const QString &command) |
80 | : cmd(command) |
81 | {} |
82 | |
83 | virtual ~V4CommandHandler() |
84 | {} |
85 | |
86 | QString command() const { return cmd; } |
87 | |
88 | void handle(const QJsonObject &request, QV4DebugServiceImpl *s) |
89 | { |
90 | TRACE_PROTOCOL(qDebug() << "handling command" << command() << "..." ); |
91 | |
92 | req = request; |
93 | seq = req.value(key: QLatin1String("seq" )); |
94 | debugService = s; |
95 | |
96 | handleRequest(); |
97 | if (!response.isEmpty()) { |
98 | response[QLatin1String("type" )] = QStringLiteral("response" ); |
99 | debugService->send(v4Payload: response); |
100 | } |
101 | |
102 | debugService = nullptr; |
103 | seq = QJsonValue(); |
104 | req = QJsonObject(); |
105 | response = QJsonObject(); |
106 | } |
107 | |
108 | virtual void handleRequest() = 0; |
109 | |
110 | protected: |
111 | void addCommand() { response.insert(QStringLiteral("command" ), value: cmd); } |
112 | void addRequestSequence() { response.insert(QStringLiteral("request_seq" ), value: seq); } |
113 | void addSuccess(bool success) { response.insert(QStringLiteral("success" ), value: success); } |
114 | void addBody(const QJsonValue &body) |
115 | { |
116 | response.insert(QStringLiteral("body" ), value: body); |
117 | } |
118 | |
119 | void addRunning() |
120 | { |
121 | response.insert(QStringLiteral("running" ), value: debugService->debuggerAgent.isRunning()); |
122 | } |
123 | |
124 | void createErrorResponse(const QString &msg) |
125 | { |
126 | QJsonValue command = req.value(key: QLatin1String("command" )); |
127 | response.insert(QStringLiteral("command" ), value: command); |
128 | addRequestSequence(); |
129 | addSuccess(success: false); |
130 | addRunning(); |
131 | response.insert(QStringLiteral("message" ), value: msg); |
132 | } |
133 | |
134 | int requestSequenceNr() const |
135 | { return seq.toInt(defaultValue: -1); } |
136 | |
137 | protected: |
138 | QString cmd; |
139 | QJsonObject req; |
140 | QJsonValue seq; |
141 | QV4DebugServiceImpl *debugService; |
142 | QJsonObject response; |
143 | }; |
144 | |
145 | class UnknownV4CommandHandler: public V4CommandHandler |
146 | { |
147 | public: |
148 | UnknownV4CommandHandler(): V4CommandHandler(QString()) {} |
149 | |
150 | void handleRequest() override |
151 | { |
152 | QString msg = QLatin1String("unimplemented command \"" ) |
153 | + req.value(key: QLatin1String("command" )).toString() |
154 | + QLatin1Char('"'); |
155 | createErrorResponse(msg); |
156 | } |
157 | }; |
158 | |
159 | namespace { |
160 | class V4VersionRequest: public V4CommandHandler |
161 | { |
162 | public: |
163 | V4VersionRequest(): V4CommandHandler(QStringLiteral("version" )) {} |
164 | |
165 | void handleRequest() override |
166 | { |
167 | addCommand(); |
168 | addRequestSequence(); |
169 | addSuccess(success: true); |
170 | addRunning(); |
171 | QJsonObject body; |
172 | body.insert(QStringLiteral("V8Version" ), |
173 | value: QLatin1String("this is not V8, this is V4 in Qt " QT_VERSION_STR)); |
174 | body.insert(QStringLiteral("UnpausedEvaluate" ), value: true); |
175 | body.insert(QStringLiteral("ContextEvaluate" ), value: true); |
176 | body.insert(QStringLiteral("ChangeBreakpoint" ), value: true); |
177 | addBody(body); |
178 | } |
179 | }; |
180 | |
181 | class V4BreakPointRequest: public V4CommandHandler |
182 | { |
183 | public: |
184 | V4BreakPointRequest(const QString &name): V4CommandHandler(name) {} |
185 | |
186 | void handleRequest() final |
187 | { |
188 | // Other types are currently not supported |
189 | m_type = QStringLiteral("scriptRegExp" ); |
190 | |
191 | // decypher the payload: |
192 | m_args = req.value(key: QLatin1String("arguments" )).toObject(); |
193 | if (m_args.isEmpty()) { |
194 | createErrorResponse(QStringLiteral("breakpoint request with empty arguments object" )); |
195 | return; |
196 | } |
197 | |
198 | const int id = handleBreakPointRequest(); |
199 | if (id < 0) { |
200 | createErrorResponse(msg: m_error); |
201 | } else { |
202 | // response: |
203 | addCommand(); |
204 | addRequestSequence(); |
205 | addSuccess(success: true); |
206 | addRunning(); |
207 | QJsonObject body; |
208 | body.insert(QStringLiteral("type" ), value: m_type); |
209 | body.insert(QStringLiteral("breakpoint" ), value: id); |
210 | addBody(body); |
211 | } |
212 | } |
213 | |
214 | protected: |
215 | virtual int handleBreakPointRequest() = 0; |
216 | |
217 | QJsonObject m_args; |
218 | QString m_type; |
219 | QString m_error; |
220 | }; |
221 | |
222 | class V4SetBreakPointRequest: public V4BreakPointRequest |
223 | { |
224 | public: |
225 | V4SetBreakPointRequest(): V4BreakPointRequest(QStringLiteral("setbreakpoint" )) {} |
226 | |
227 | int handleBreakPointRequest() final |
228 | { |
229 | // decypher the payload: |
230 | const QString type = m_args.value(key: QLatin1String("type" )).toString(); |
231 | if (type != QLatin1String("scriptRegExp" )) { |
232 | m_error = QStringLiteral("breakpoint type \"%1\" is not implemented" ).arg(a: type); |
233 | return -1; |
234 | } |
235 | |
236 | const QString fileName = m_args.value(key: QLatin1String("target" )).toString(); |
237 | if (fileName.isEmpty()) { |
238 | m_error = QStringLiteral("breakpoint has no file name" ); |
239 | return -1; |
240 | } |
241 | |
242 | |
243 | const int line = m_args.value(key: QLatin1String("line" )).toInt(defaultValue: -1); |
244 | if (line < 0) { |
245 | m_error = QStringLiteral("breakpoint has an invalid line number" ); |
246 | return -1; |
247 | } |
248 | |
249 | const bool enabled = m_args.value(QStringLiteral("enabled" )).toBool(defaultValue: true); |
250 | const QString condition = m_args.value(QStringLiteral("condition" )).toString(); |
251 | |
252 | // set the break point: |
253 | return debugService->debuggerAgent.addBreakPoint(fileName, lineNumber: line + 1, enabled, condition); |
254 | |
255 | // It's undocumented, but V8 sends back an actual_locations array too. However, our |
256 | // Debugger currently doesn't tell us when it resolved a breakpoint, so we'll leave them |
257 | // pending until the breakpoint is hit for the first time. |
258 | } |
259 | }; |
260 | |
261 | class V4ClearBreakPointRequest: public V4BreakPointRequest |
262 | { |
263 | public: |
264 | V4ClearBreakPointRequest(): V4BreakPointRequest(QStringLiteral("clearbreakpoint" )) {} |
265 | |
266 | int handleBreakPointRequest() final |
267 | { |
268 | const int id = m_args.value(key: QLatin1String("breakpoint" )).toInt(defaultValue: -1); |
269 | if (id < 0) |
270 | m_error = QStringLiteral("breakpoint has an invalid number" ); |
271 | else // remove the break point: |
272 | debugService->debuggerAgent.removeBreakPoint(id); |
273 | |
274 | return id; |
275 | } |
276 | }; |
277 | |
278 | class V4ChangeBreakPointRequest: public V4BreakPointRequest |
279 | { |
280 | public: |
281 | V4ChangeBreakPointRequest(): V4BreakPointRequest(QStringLiteral("changebreakpoint" )) {} |
282 | |
283 | int handleBreakPointRequest() final |
284 | { |
285 | const int id = m_args.value(key: QLatin1String("breakpoint" )).toInt(defaultValue: -1); |
286 | if (id < 0) { |
287 | m_error = QStringLiteral("breakpoint has an invalid number" ); |
288 | return id; |
289 | } |
290 | |
291 | const QJsonValue enabled = m_args.value(key: QLatin1String("enabled" )); |
292 | if (!enabled.isBool()) { |
293 | m_error = QStringLiteral("missing bool \"enabled\" in breakpoint change request" ); |
294 | return -1; |
295 | } |
296 | |
297 | // enable or disable the break point: |
298 | debugService->debuggerAgent.enableBreakPoint(id, onoff: enabled.toBool()); |
299 | return id; |
300 | } |
301 | }; |
302 | |
303 | class V4BacktraceRequest: public V4CommandHandler |
304 | { |
305 | public: |
306 | V4BacktraceRequest(): V4CommandHandler(QStringLiteral("backtrace" )) {} |
307 | |
308 | void handleRequest() override |
309 | { |
310 | // decypher the payload: |
311 | |
312 | QJsonObject arguments = req.value(key: QLatin1String("arguments" )).toObject(); |
313 | int fromFrame = arguments.value(key: QLatin1String("fromFrame" )).toInt(defaultValue: 0); |
314 | int toFrame = arguments.value(key: QLatin1String("toFrame" )).toInt(defaultValue: fromFrame + 10); |
315 | // no idea what the bottom property is for, so we'll ignore it. |
316 | |
317 | QV4Debugger *debugger = debugService->debuggerAgent.pausedDebugger(); |
318 | if (!debugger) { |
319 | createErrorResponse(QStringLiteral("Debugger has to be paused to retrieve backtraces." )); |
320 | return; |
321 | } |
322 | |
323 | BacktraceJob job(debugger->collector(), fromFrame, toFrame); |
324 | debugger->runInEngine(job: &job); |
325 | |
326 | // response: |
327 | addCommand(); |
328 | addRequestSequence(); |
329 | addSuccess(success: true); |
330 | addRunning(); |
331 | addBody(body: job.returnValue()); |
332 | } |
333 | }; |
334 | |
335 | class V4FrameRequest: public V4CommandHandler |
336 | { |
337 | public: |
338 | V4FrameRequest(): V4CommandHandler(QStringLiteral("frame" )) {} |
339 | |
340 | void handleRequest() override |
341 | { |
342 | // decypher the payload: |
343 | QJsonObject arguments = req.value(key: QLatin1String("arguments" )).toObject(); |
344 | const int frameNr = arguments.value(key: QLatin1String("number" )).toInt( |
345 | defaultValue: debugService->selectedFrame()); |
346 | |
347 | QV4Debugger *debugger = debugService->debuggerAgent.pausedDebugger(); |
348 | if (!debugger) { |
349 | createErrorResponse(QStringLiteral("Debugger has to be paused to retrieve frames." )); |
350 | return; |
351 | } |
352 | |
353 | if (frameNr < 0) { |
354 | createErrorResponse(QStringLiteral("frame command has invalid frame number" )); |
355 | return; |
356 | } |
357 | |
358 | FrameJob job(debugger->collector(), frameNr); |
359 | debugger->runInEngine(job: &job); |
360 | if (!job.wasSuccessful()) { |
361 | createErrorResponse(QStringLiteral("frame retrieval failed" )); |
362 | return; |
363 | } |
364 | |
365 | debugService->selectFrame(frameNr); |
366 | |
367 | // response: |
368 | addCommand(); |
369 | addRequestSequence(); |
370 | addSuccess(success: true); |
371 | addRunning(); |
372 | addBody(body: job.returnValue()); |
373 | } |
374 | }; |
375 | |
376 | class V4ScopeRequest: public V4CommandHandler |
377 | { |
378 | public: |
379 | V4ScopeRequest(): V4CommandHandler(QStringLiteral("scope" )) {} |
380 | |
381 | void handleRequest() override |
382 | { |
383 | // decypher the payload: |
384 | QJsonObject arguments = req.value(key: QLatin1String("arguments" )).toObject(); |
385 | const int frameNr = arguments.value(key: QLatin1String("frameNumber" )).toInt( |
386 | defaultValue: debugService->selectedFrame()); |
387 | const int scopeNr = arguments.value(key: QLatin1String("number" )).toInt(defaultValue: 0); |
388 | |
389 | QV4Debugger *debugger = debugService->debuggerAgent.pausedDebugger(); |
390 | if (!debugger) { |
391 | createErrorResponse(QStringLiteral("Debugger has to be paused to retrieve scope." )); |
392 | return; |
393 | } |
394 | |
395 | if (frameNr < 0) { |
396 | createErrorResponse(QStringLiteral("scope command has invalid frame number" )); |
397 | return; |
398 | } |
399 | if (scopeNr < 0) { |
400 | createErrorResponse(QStringLiteral("scope command has invalid scope number" )); |
401 | return; |
402 | } |
403 | |
404 | ScopeJob job(debugger->collector(), frameNr, scopeNr); |
405 | debugger->runInEngine(job: &job); |
406 | if (!job.wasSuccessful()) { |
407 | createErrorResponse(QStringLiteral("scope retrieval failed" )); |
408 | return; |
409 | } |
410 | |
411 | // response: |
412 | addCommand(); |
413 | addRequestSequence(); |
414 | addSuccess(success: true); |
415 | addRunning(); |
416 | addBody(body: job.returnValue()); |
417 | } |
418 | }; |
419 | |
420 | class V4LookupRequest: public V4CommandHandler |
421 | { |
422 | public: |
423 | V4LookupRequest(): V4CommandHandler(QStringLiteral("lookup" )) {} |
424 | |
425 | void handleRequest() override |
426 | { |
427 | // decypher the payload: |
428 | QJsonObject arguments = req.value(key: QLatin1String("arguments" )).toObject(); |
429 | QJsonArray handles = arguments.value(key: QLatin1String("handles" )).toArray(); |
430 | |
431 | QV4Debugger *debugger = debugService->debuggerAgent.pausedDebugger(); |
432 | if (!debugger) { |
433 | const QList<QV4Debugger *> &debuggers = debugService->debuggerAgent.debuggers(); |
434 | if (debuggers.count() > 1) { |
435 | createErrorResponse(QStringLiteral("Cannot lookup values if multiple debuggers are running and none is paused" )); |
436 | return; |
437 | } else if (debuggers.count() == 0) { |
438 | createErrorResponse(QStringLiteral("No debuggers available to lookup values" )); |
439 | return; |
440 | } |
441 | debugger = debuggers.first(); |
442 | } |
443 | |
444 | ValueLookupJob job(handles, debugger->collector()); |
445 | debugger->runInEngine(job: &job); |
446 | if (!job.exceptionMessage().isEmpty()) { |
447 | createErrorResponse(msg: job.exceptionMessage()); |
448 | } else { |
449 | // response: |
450 | addCommand(); |
451 | addRequestSequence(); |
452 | addSuccess(success: true); |
453 | addRunning(); |
454 | addBody(body: job.returnValue()); |
455 | } |
456 | } |
457 | }; |
458 | |
459 | class V4ContinueRequest: public V4CommandHandler |
460 | { |
461 | public: |
462 | V4ContinueRequest(): V4CommandHandler(QStringLiteral("continue" )) {} |
463 | |
464 | void handleRequest() override |
465 | { |
466 | // decypher the payload: |
467 | QJsonObject arguments = req.value(key: QLatin1String("arguments" )).toObject(); |
468 | |
469 | QV4Debugger *debugger = debugService->debuggerAgent.pausedDebugger(); |
470 | if (!debugger) { |
471 | createErrorResponse(QStringLiteral("Debugger has to be paused in order to continue." )); |
472 | return; |
473 | } |
474 | debugService->debuggerAgent.clearAllPauseRequests(); |
475 | |
476 | if (arguments.empty()) { |
477 | debugger->resume(speed: QV4Debugger::FullThrottle); |
478 | } else { |
479 | QJsonObject arguments = req.value(key: QLatin1String("arguments" )).toObject(); |
480 | QString stepAction = arguments.value(key: QLatin1String("stepaction" )).toString(); |
481 | const int stepcount = arguments.value(key: QLatin1String("stepcount" )).toInt(defaultValue: 1); |
482 | if (stepcount != 1) |
483 | qWarning() << "Step count other than 1 is not supported." ; |
484 | |
485 | if (stepAction == QLatin1String("in" )) { |
486 | debugger->resume(speed: QV4Debugger::StepIn); |
487 | } else if (stepAction == QLatin1String("out" )) { |
488 | debugger->resume(speed: QV4Debugger::StepOut); |
489 | } else if (stepAction == QLatin1String("next" )) { |
490 | debugger->resume(speed: QV4Debugger::StepOver); |
491 | } else { |
492 | createErrorResponse(QStringLiteral("continue command has invalid stepaction" )); |
493 | return; |
494 | } |
495 | } |
496 | |
497 | // response: |
498 | addCommand(); |
499 | addRequestSequence(); |
500 | addSuccess(success: true); |
501 | addRunning(); |
502 | } |
503 | }; |
504 | |
505 | class V4DisconnectRequest: public V4CommandHandler |
506 | { |
507 | public: |
508 | V4DisconnectRequest(): V4CommandHandler(QStringLiteral("disconnect" )) {} |
509 | |
510 | void handleRequest() override |
511 | { |
512 | debugService->debuggerAgent.removeAllBreakPoints(); |
513 | debugService->debuggerAgent.resumeAll(); |
514 | |
515 | // response: |
516 | addCommand(); |
517 | addRequestSequence(); |
518 | addSuccess(success: true); |
519 | addRunning(); |
520 | } |
521 | }; |
522 | |
523 | class V4SetExceptionBreakRequest: public V4CommandHandler |
524 | { |
525 | public: |
526 | V4SetExceptionBreakRequest(): V4CommandHandler(QStringLiteral("setexceptionbreak" )) {} |
527 | |
528 | void handleRequest() override |
529 | { |
530 | bool wasEnabled = debugService->debuggerAgent.breakOnThrow(); |
531 | |
532 | //decypher the payload: |
533 | QJsonObject arguments = req.value(key: QLatin1String("arguments" )).toObject(); |
534 | QString type = arguments.value(key: QLatin1String("type" )).toString(); |
535 | bool enabled = arguments.value(key: QLatin1String("number" )).toBool(defaultValue: !wasEnabled); |
536 | |
537 | if (type == QLatin1String("all" )) { |
538 | // that's fine |
539 | } else if (type == QLatin1String("uncaught" )) { |
540 | createErrorResponse(QStringLiteral("breaking only on uncaught exceptions is not supported yet" )); |
541 | return; |
542 | } else { |
543 | createErrorResponse(QStringLiteral("invalid type for break on exception" )); |
544 | return; |
545 | } |
546 | |
547 | // do it: |
548 | debugService->debuggerAgent.setBreakOnThrow(enabled); |
549 | |
550 | QJsonObject body; |
551 | body[QLatin1String("type" )] = type; |
552 | body[QLatin1String("enabled" )] = debugService->debuggerAgent.breakOnThrow(); |
553 | |
554 | // response: |
555 | addBody(body); |
556 | addRunning(); |
557 | addSuccess(success: true); |
558 | addRequestSequence(); |
559 | addCommand(); |
560 | } |
561 | }; |
562 | |
563 | class V4ScriptsRequest: public V4CommandHandler |
564 | { |
565 | public: |
566 | V4ScriptsRequest(): V4CommandHandler(QStringLiteral("scripts" )) {} |
567 | |
568 | void handleRequest() override |
569 | { |
570 | //decypher the payload: |
571 | QJsonObject arguments = req.value(key: QLatin1String("arguments" )).toObject(); |
572 | int types = arguments.value(key: QLatin1String("types" )).toInt(defaultValue: -1); |
573 | if (types < 0 || types > 7) { |
574 | createErrorResponse(QStringLiteral("invalid types value in scripts command" )); |
575 | return; |
576 | } else if (types != 4) { |
577 | createErrorResponse(QStringLiteral("unsupported types value in scripts command" )); |
578 | return; |
579 | } |
580 | |
581 | // do it: |
582 | QV4Debugger *debugger = debugService->debuggerAgent.pausedDebugger(); |
583 | if (!debugger) { |
584 | createErrorResponse(QStringLiteral("Debugger has to be paused to retrieve scripts." )); |
585 | return; |
586 | } |
587 | |
588 | GatherSourcesJob job(debugger->engine()); |
589 | debugger->runInEngine(job: &job); |
590 | |
591 | QJsonArray body; |
592 | for (const QString &source : job.result()) { |
593 | QJsonObject src; |
594 | src[QLatin1String("name" )] = source; |
595 | src[QLatin1String("scriptType" )] = 4; |
596 | body.append(value: src); |
597 | } |
598 | |
599 | addSuccess(success: true); |
600 | addRunning(); |
601 | addBody(body); |
602 | addCommand(); |
603 | addRequestSequence(); |
604 | } |
605 | }; |
606 | |
607 | // Request: |
608 | // { |
609 | // "seq": 4, |
610 | // "type": "request", |
611 | // "command": "evaluate", |
612 | // "arguments": { |
613 | // "expression": "a", |
614 | // "frame": 0 |
615 | // } |
616 | // } |
617 | // |
618 | // Response: |
619 | // { |
620 | // "body": { |
621 | // "handle": 3, |
622 | // "type": "number", |
623 | // "value": 1 |
624 | // }, |
625 | // "command": "evaluate", |
626 | // "refs": [], |
627 | // "request_seq": 4, |
628 | // "running": false, |
629 | // "seq": 5, |
630 | // "success": true, |
631 | // "type": "response" |
632 | // } |
633 | // |
634 | // The "value" key in "body" is the result of evaluating the expression in the request. |
635 | class V4EvaluateRequest: public V4CommandHandler |
636 | { |
637 | public: |
638 | V4EvaluateRequest(): V4CommandHandler(QStringLiteral("evaluate" )) {} |
639 | |
640 | void handleRequest() override |
641 | { |
642 | QJsonObject arguments = req.value(key: QLatin1String("arguments" )).toObject(); |
643 | QString expression = arguments.value(key: QLatin1String("expression" )).toString(); |
644 | int context = arguments.value(key: QLatin1String("context" )).toInt(defaultValue: -1); |
645 | int frame = -1; |
646 | |
647 | QV4Debugger *debugger = debugService->debuggerAgent.pausedDebugger(); |
648 | if (!debugger) { |
649 | const QList<QV4Debugger *> &debuggers = debugService->debuggerAgent.debuggers(); |
650 | if (debuggers.count() > 1) { |
651 | createErrorResponse(QStringLiteral("Cannot evaluate expressions if multiple debuggers are running and none is paused" )); |
652 | return; |
653 | } else if (debuggers.count() == 0) { |
654 | createErrorResponse(QStringLiteral("No debuggers available to evaluate expressions" )); |
655 | return; |
656 | } |
657 | debugger = debuggers.first(); |
658 | } else { |
659 | frame = arguments.value(key: QLatin1String("frame" )).toInt(defaultValue: 0); |
660 | } |
661 | |
662 | ExpressionEvalJob job(debugger->engine(), frame, context, expression, |
663 | debugger->collector()); |
664 | debugger->runInEngine(job: &job); |
665 | if (job.hasExeption()) { |
666 | createErrorResponse(msg: job.exceptionMessage()); |
667 | } else { |
668 | addCommand(); |
669 | addRequestSequence(); |
670 | addSuccess(success: true); |
671 | addRunning(); |
672 | addBody(body: job.returnValue()); |
673 | } |
674 | } |
675 | }; |
676 | } // anonymous namespace |
677 | |
678 | void QV4DebugServiceImpl::addHandler(V4CommandHandler* handler) |
679 | { |
680 | handlers[handler->command()] = handler; |
681 | } |
682 | |
683 | V4CommandHandler *QV4DebugServiceImpl::v4CommandHandler(const QString &command) const |
684 | { |
685 | V4CommandHandler *handler = handlers.value(key: command, defaultValue: 0); |
686 | if (handler) |
687 | return handler; |
688 | else |
689 | return unknownV4CommandHandler.data(); |
690 | } |
691 | |
692 | QV4DebugServiceImpl::QV4DebugServiceImpl(QObject *parent) : |
693 | QQmlConfigurableDebugService<QV4DebugService>(1, parent), |
694 | debuggerAgent(this), theSelectedFrame(0), |
695 | unknownV4CommandHandler(new UnknownV4CommandHandler) |
696 | { |
697 | addHandler(handler: new V4VersionRequest); |
698 | addHandler(handler: new V4SetBreakPointRequest); |
699 | addHandler(handler: new V4ClearBreakPointRequest); |
700 | addHandler(handler: new V4ChangeBreakPointRequest); |
701 | addHandler(handler: new V4BacktraceRequest); |
702 | addHandler(handler: new V4FrameRequest); |
703 | addHandler(handler: new V4ScopeRequest); |
704 | addHandler(handler: new V4LookupRequest); |
705 | addHandler(handler: new V4ContinueRequest); |
706 | addHandler(handler: new V4DisconnectRequest); |
707 | addHandler(handler: new V4SetExceptionBreakRequest); |
708 | addHandler(handler: new V4ScriptsRequest); |
709 | addHandler(handler: new V4EvaluateRequest); |
710 | } |
711 | |
712 | QV4DebugServiceImpl::~QV4DebugServiceImpl() |
713 | { |
714 | qDeleteAll(c: handlers); |
715 | } |
716 | |
717 | void QV4DebugServiceImpl::engineAdded(QJSEngine *engine) |
718 | { |
719 | QMutexLocker lock(&m_configMutex); |
720 | if (engine) { |
721 | QV4::ExecutionEngine *ee = engine->handle(); |
722 | if (QQmlDebugConnector *server = QQmlDebugConnector::instance()) { |
723 | if (ee) { |
724 | QV4Debugger *debugger = new QV4Debugger(ee); |
725 | if (state() == Enabled) |
726 | ee->setDebugger(debugger); |
727 | debuggerAgent.addDebugger(debugger); |
728 | debuggerAgent.moveToThread(thread: server->thread()); |
729 | } |
730 | } |
731 | } |
732 | QQmlConfigurableDebugService<QV4DebugService>::engineAdded(engine); |
733 | } |
734 | |
735 | void QV4DebugServiceImpl::engineAboutToBeRemoved(QJSEngine *engine) |
736 | { |
737 | QMutexLocker lock(&m_configMutex); |
738 | if (engine){ |
739 | const QV4::ExecutionEngine *ee = engine->handle(); |
740 | if (ee) { |
741 | QV4Debugger *debugger = qobject_cast<QV4Debugger *>(object: ee->debugger()); |
742 | if (debugger) |
743 | debuggerAgent.removeDebugger(debugger); |
744 | } |
745 | } |
746 | QQmlConfigurableDebugService<QV4DebugService>::engineAboutToBeRemoved(engine); |
747 | } |
748 | |
749 | void QV4DebugServiceImpl::stateAboutToBeChanged(State state) |
750 | { |
751 | QMutexLocker lock(&m_configMutex); |
752 | if (state == Enabled) { |
753 | const auto debuggers = debuggerAgent.debuggers(); |
754 | for (QV4Debugger *debugger : debuggers) { |
755 | QV4::ExecutionEngine *ee = debugger->engine(); |
756 | if (!ee->debugger()) |
757 | ee->setDebugger(debugger); |
758 | } |
759 | } |
760 | QQmlConfigurableDebugService<QV4DebugService>::stateAboutToBeChanged(state); |
761 | } |
762 | |
763 | void QV4DebugServiceImpl::signalEmitted(const QString &signal) |
764 | { |
765 | //This function is only called by QQmlBoundSignal |
766 | //only if there is a slot connected to the signal. Hence, there |
767 | //is no need for additional check. |
768 | |
769 | //Parse just the name and remove the class info |
770 | //Normalize to Lower case. |
771 | QString signalName = signal.left(n: signal.indexOf(c: QLatin1Char('('))).toLower(); |
772 | |
773 | for (const QString &signal : qAsConst(t&: breakOnSignals)) { |
774 | if (signal == signalName) { |
775 | // TODO: pause debugger |
776 | break; |
777 | } |
778 | } |
779 | } |
780 | |
781 | void QV4DebugServiceImpl::messageReceived(const QByteArray &message) |
782 | { |
783 | QMutexLocker lock(&m_configMutex); |
784 | |
785 | QQmlDebugPacket ms(message); |
786 | QByteArray ; |
787 | ms >> header; |
788 | |
789 | TRACE_PROTOCOL(qDebug() << "received message with header" << header); |
790 | |
791 | if (header == "V8DEBUG" ) { |
792 | QByteArray type; |
793 | QByteArray payload; |
794 | ms >> type >> payload; |
795 | TRACE_PROTOCOL(qDebug() << "... type:" << type); |
796 | |
797 | if (type == V4_CONNECT) { |
798 | QJsonObject parameters = QJsonDocument::fromJson(json: payload).object(); |
799 | Q_UNUSED(parameters); // For future protocol changes |
800 | |
801 | emit messageToClient(name: name(), message: packMessage(command: type)); |
802 | stopWaiting(); |
803 | } else if (type == V4_PAUSE) { |
804 | debuggerAgent.pauseAll(); |
805 | sendSomethingToSomebody(type); |
806 | } else if (type == V4_BREAK_ON_SIGNAL) { |
807 | QByteArray signal; |
808 | bool enabled; |
809 | ms >> signal >> enabled; |
810 | //Normalize to lower case. |
811 | QString signalName(QString::fromUtf8(str: signal).toLower()); |
812 | if (enabled) |
813 | breakOnSignals.append(t: signalName); |
814 | else |
815 | breakOnSignals.removeOne(t: signalName); |
816 | } else if (type == "v8request" ) { |
817 | handleV4Request(payload); |
818 | } else if (type == V4_DISCONNECT) { |
819 | TRACE_PROTOCOL(qDebug() << "... payload:" << payload.constData()); |
820 | handleV4Request(payload); |
821 | } else { |
822 | sendSomethingToSomebody(type, magicNumber: 0); |
823 | } |
824 | } |
825 | } |
826 | |
827 | void QV4DebugServiceImpl::sendSomethingToSomebody(const char *type, int magicNumber) |
828 | { |
829 | QQmlDebugPacket rs; |
830 | rs << QByteArray(type) |
831 | << QByteArray::number(int(version())) << QByteArray::number(magicNumber); |
832 | emit messageToClient(name: name(), message: packMessage(command: type, message: rs.data())); |
833 | } |
834 | |
835 | void QV4DebugServiceImpl::handleV4Request(const QByteArray &payload) |
836 | { |
837 | TRACE_PROTOCOL(qDebug() << "v8request, payload:" << payload.constData()); |
838 | |
839 | QJsonDocument request = QJsonDocument::fromJson(json: payload); |
840 | QJsonObject o = request.object(); |
841 | QJsonValue type = o.value(key: QLatin1String("type" )); |
842 | if (type.toString() == QLatin1String("request" )) { |
843 | QJsonValue command = o.value(key: QLatin1String("command" )); |
844 | V4CommandHandler *h = v4CommandHandler(command: command.toString()); |
845 | if (h) |
846 | h->handle(request: o, s: this); |
847 | } |
848 | } |
849 | |
850 | QByteArray QV4DebugServiceImpl::packMessage(const QByteArray &command, const QByteArray &message) |
851 | { |
852 | QQmlDebugPacket rs; |
853 | static const QByteArray cmd("V8DEBUG" ); |
854 | rs << cmd << command << message; |
855 | return rs.data(); |
856 | } |
857 | |
858 | void QV4DebugServiceImpl::send(QJsonObject v4Payload) |
859 | { |
860 | v4Payload[QLatin1String("seq" )] = sequence++; |
861 | QJsonDocument doc; |
862 | doc.setObject(v4Payload); |
863 | #ifdef NO_PROTOCOL_TRACING |
864 | QByteArray responseData = doc.toJson(format: QJsonDocument::Compact); |
865 | #else |
866 | QByteArray responseData = doc.toJson(QJsonDocument::Indented); |
867 | #endif |
868 | |
869 | TRACE_PROTOCOL(qDebug() << "sending response for:" << responseData.constData() << endl); |
870 | |
871 | emit messageToClient(name: name(), message: packMessage(command: "v8message" , message: responseData)); |
872 | } |
873 | |
874 | void QV4DebugServiceImpl::selectFrame(int frameNr) |
875 | { |
876 | theSelectedFrame = frameNr; |
877 | } |
878 | |
879 | int QV4DebugServiceImpl::selectedFrame() const |
880 | { |
881 | return theSelectedFrame; |
882 | } |
883 | |
884 | QT_END_NAMESPACE |
885 | |
886 | #include "moc_qv4debugservice.cpp" |
887 | |