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