Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlnativedebugservice.cpp
Go to the documentation of this file.
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// Qt-Security score:significant
4
6
7#include <private/qqmldebugconnector_p.h>
8#include <private/qqmldebugserviceinterfaces_p.h>
9#include <private/qv4debugging_p.h>
10#include <private/qv4debugging_p.h>
11#include <private/qv4engine_p.h>
12#include <private/qv4identifierhash_p.h>
13#include <private/qv4identifiertable_p.h>
14#include <private/qv4objectiterator_p.h>
15#include <private/qv4runtime_p.h>
16#include <private/qv4script_p.h>
17#include <private/qv4stackframe_p.h>
18#include <private/qv4string_p.h>
19#include <private/qversionedpacket_p.h>
20
21#include <QtQml/qjsengine.h>
22#include <QtCore/qjsonarray.h>
23#include <QtCore/qjsondocument.h>
24#include <QtCore/qjsonobject.h>
25#include <QtCore/qjsonvalue.h>
26#include <QtCore/qvector.h>
27#include <QtCore/qpointer.h>
28
29//#define TRACE_PROTOCOL(s) qDebug() << s
30#define TRACE_PROTOCOL(s)
31
33
34using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
35
37{
38public:
39 BreakPoint() : id(-1), lineNumber(-1), enabled(false), ignoreCount(0), hitCount(0) {}
40 bool isValid() const { return lineNumber >= 0 && !fileName.isEmpty(); }
41
42 int id;
45 bool enabled;
48
50};
51
52inline size_t qHash(const BreakPoint &b, size_t seed = 0) noexcept
53{
54 return qHash(b.fileName, seed) ^ b.lineNumber;
55}
56
57inline bool operator==(const BreakPoint &a, const BreakPoint &b)
58{
59 return a.lineNumber == b.lineNumber && a.fileName == b.fileName
60 && a.enabled == b.enabled && a.condition == b.condition
62}
63
64static void setError(QJsonObject *response, const QString &msg)
65{
66 response->insert(QStringLiteral("type"), QStringLiteral("error"));
67 response->insert(QStringLiteral("msg"), msg);
68}
69
70class NativeDebugger;
71
73{
74public:
75 Collector(QV4::ExecutionEngine *engine)
77 {}
78
79 void collect(QJsonArray *output, const QString &parentIName, const QString &name,
80 const QV4::Value &value);
81
82 bool isExpanded(const QString &iname) const { return m_expanded.contains(iname); }
83
84public:
88};
89
90// Encapsulate Breakpoint handling
91// Could be made per-NativeDebugger (i.e. per execution engine, if needed)
93{
94public:
96
97 void handleSetBreakpoint(QJsonObject *response, const QJsonObject &arguments);
98 void handleRemoveBreakpoint(QJsonObject *response, const QJsonObject &arguments);
99
100 void removeBreakPoint(int id);
101 void enableBreakPoint(int id, bool onoff);
102
103 void setBreakOnThrow(bool onoff);
108};
109
110void BreakPointHandler::handleSetBreakpoint(QJsonObject *response, const QJsonObject &arguments)
111{
112 TRACE_PROTOCOL("SET BREAKPOINT" << arguments);
113 QString type = arguments.value(QLatin1String("type")).toString();
114
115 QString fileName = arguments.value(QLatin1String("file")).toString();
116 if (fileName.isEmpty()) {
117 setError(response, QStringLiteral("breakpoint has no file name"));
118 return;
119 }
120
121 int line = arguments.value(QLatin1String("line")).toInt(-1);
122 if (line < 0) {
123 setError(response, QStringLiteral("breakpoint has an invalid line number"));
124 return;
125 }
126
127 BreakPoint bp;
128 bp.id = m_lastBreakpoint++;
129 bp.fileName = fileName.mid(fileName.lastIndexOf('/') + 1);
130 bp.lineNumber = line;
131 bp.enabled = arguments.value(QLatin1String("enabled")).toBool(true);
132 bp.condition = arguments.value(QLatin1String("condition")).toString();
133 bp.ignoreCount = arguments.value(QLatin1String("ignorecount")).toInt();
134 m_breakPoints.append(bp);
135
136 m_haveBreakPoints = true;
137
138 response->insert(QStringLiteral("type"), type);
139 response->insert(QStringLiteral("breakpoint"), bp.id);
140}
141
142void BreakPointHandler::handleRemoveBreakpoint(QJsonObject *response, const QJsonObject &arguments)
143{
144 int id = arguments.value(QLatin1String("id")).toInt();
146 response->insert(QStringLiteral("id"), id);
147}
148
150{
151public:
152 NativeDebugger(QQmlNativeDebugServiceImpl *service, QV4::ExecutionEngine *engine);
153
154 void signalEmitted(const QString &signal);
155
156 QV4::ExecutionEngine *engine() const { return m_engine; }
157
159 return m_pauseRequested
160 || m_service->m_breakHandler->m_haveBreakPoints
161 || m_stepping >= StepOver;
162 }
163
166 void leavingFunction(const QV4::ReturnedValue &retVal) override;
168
169 void handleCommand(QJsonObject *response, const QString &cmd, const QJsonObject &arguments);
170
171private:
172 void handleBacktrace(QJsonObject *response, const QJsonObject &arguments);
173 void handleVariables(QJsonObject *response, const QJsonObject &arguments);
174 void handleExpressions(QJsonObject *response, const QJsonObject &arguments);
175
176 void handleDebuggerDeleted(QObject *debugger);
177
178 QV4::ReturnedValue evaluateExpression(const QString &expression);
179 bool checkCondition(const QString &expression);
180
181 QStringList breakOnSignals;
182
183 enum Speed {
184 NotStepping = 0,
185 StepOut,
186 StepOver,
187 StepIn,
188 };
189
190 void pauseAndWait();
191 void pause();
192 void handleContinue(QJsonObject *reponse, Speed speed);
193
194 QV4::Function *getFunction() const;
195
196 bool reallyHitTheBreakPoint(const QV4::Function *function, int lineNumber);
197
198 QV4::ExecutionEngine *m_engine;
200 QV4::CppStackFrame *m_currentFrame = nullptr;
201 Speed m_stepping;
202 bool m_pauseRequested;
203 bool m_runningJob;
204
205 QV4::PersistentValue m_returnedValue;
206};
207
208bool NativeDebugger::checkCondition(const QString &expression)
209{
210 QV4::Scope scope(m_engine);
211 QV4::ScopedValue r(scope, evaluateExpression(expression));
212 return r->booleanValue();
213}
214
215QV4::ReturnedValue NativeDebugger::evaluateExpression(const QString &expression)
216{
217 QV4::Scope scope(m_engine);
218 m_runningJob = true;
219
220 QV4::ExecutionContext *ctx = m_engine->currentStackFrame ? m_engine->currentContext()
221 : m_engine->scriptContext();
222
223 QV4::Script script(ctx, QV4::Compiler::ContextType::Eval, expression);
224 if (const QV4::Function *function = m_engine->currentStackFrame
225 ? m_engine->currentStackFrame->v4Function : m_engine->globalCode) {
226 script.setStrictMode(function->isStrict());
227 }
228 // In order for property lookups in QML to work, we need to disable fast v4 lookups.
229 // That is a side-effect of inheritContext.
230 script.setInheritContext();
231 script.parse();
232 if (!m_engine->hasException) {
233 if (m_engine->currentStackFrame) {
234 QV4::ScopedValue thisObject(scope, m_engine->currentStackFrame->thisObject());
235 script.run(thisObject);
236 } else {
237 script.run();
238 }
239 }
240
241 m_runningJob = false;
242 return QV4::Encode::undefined();
243}
244
245NativeDebugger::NativeDebugger(QQmlNativeDebugServiceImpl *service, QV4::ExecutionEngine *engine)
247{
248 m_stepping = NotStepping;
249 m_pauseRequested = false;
250 m_runningJob = false;
251 m_service = service;
252 m_engine = engine;
253 TRACE_PROTOCOL("Creating native debugger");
254}
255
256void NativeDebugger::signalEmitted(const QString &signal)
257{
258 //This function is only called by QQmlBoundSignal
259 //only if there is a slot connected to the signal. Hence, there
260 //is no need for additional check.
261
262 //Parse just the name and remove the class info
263 //Normalize to Lower case.
264 QString signalName = signal.left(signal.indexOf(QLatin1Char('('))).toLower();
265
266 for (const QString &signal : std::as_const(breakOnSignals)) {
267 if (signal == signalName) {
268 // TODO: pause debugger
269 break;
270 }
271 }
272}
273
274void NativeDebugger::handleCommand(QJsonObject *response, const QString &cmd,
275 const QJsonObject &arguments)
276{
277 if (cmd == QLatin1String("backtrace"))
278 handleBacktrace(response, arguments);
279 else if (cmd == QLatin1String("variables"))
280 handleVariables(response, arguments);
281 else if (cmd == QLatin1String("expressions"))
282 handleExpressions(response, arguments);
283 else if (cmd == QLatin1String("stepin"))
284 handleContinue(response, StepIn);
285 else if (cmd == QLatin1String("stepout"))
286 handleContinue(response, StepOut);
287 else if (cmd == QLatin1String("stepover"))
288 handleContinue(response, StepOver);
289 else if (cmd == QLatin1String("continue"))
290 handleContinue(response, NotStepping);
291}
292
293static QString encodeFrame(QV4::CppStackFrame *f)
294{
295 QQmlDebugPacket ds;
296 ds << quintptr(f);
297 return QString::fromLatin1(ds.data().toHex());
298}
299
300static void decodeFrame(const QString &f, QV4::CppStackFrame **frame)
301{
302 quintptr rawFrame;
303 QQmlDebugPacket ds(QByteArray::fromHex(f.toLatin1()));
304 ds >> rawFrame;
305 *frame = reinterpret_cast<QV4::CppStackFrame *>(rawFrame);
306}
307
308void NativeDebugger::handleBacktrace(QJsonObject *response, const QJsonObject &arguments)
309{
310 int limit = arguments.value(QLatin1String("limit")).toInt(0);
311
312 QJsonArray frameArray;
313 QV4::CppStackFrame *f= m_engine->currentStackFrame;
314 for (int i = 0; i < limit && f; ++i) {
315 QV4::Function *function = f->v4Function;
316
317 QJsonObject frame;
318 frame.insert(QStringLiteral("language"), QStringLiteral("js"));
319 frame.insert(QStringLiteral("context"), encodeFrame(f));
320
321 if (QV4::Heap::String *functionName = function->name())
322 frame.insert(QStringLiteral("function"), functionName->toQString());
323 frame.insert(QStringLiteral("file"), function->sourceFile());
324
325 int line = f->lineNumber();
326 frame.insert(QStringLiteral("line"), (line < 0 ? -line : line));
327
328 frameArray.push_back(frame);
329
330 f = f->parentFrame();
331 }
332
333 response->insert(QStringLiteral("frames"), frameArray);
334}
335
336void Collector::collect(QJsonArray *out, const QString &parentIName, const QString &name,
337 const QV4::Value &value)
338{
339 QJsonObject dict;
340 QV4::Scope scope(m_engine);
341
342 QString nonEmptyName = name.isEmpty() ? QString::fromLatin1("@%1").arg(m_anonCount++) : name;
343 QString iname = parentIName + QLatin1Char('.') + nonEmptyName;
344 dict.insert(QStringLiteral("iname"), iname);
345 dict.insert(QStringLiteral("name"), nonEmptyName);
346
347 QV4::ScopedValue typeString(scope, QV4::Runtime::TypeofValue::call(m_engine, value));
348 dict.insert(QStringLiteral("type"), typeString->toQStringNoThrow());
349
350 switch (value.type()) {
351 case QV4::Value::Empty_Type:
352 dict.insert(QStringLiteral("valueencoded"), QStringLiteral("empty"));
353 dict.insert(QStringLiteral("haschild"), false);
354 break;
355 case QV4::Value::Undefined_Type:
356 dict.insert(QStringLiteral("valueencoded"), QStringLiteral("undefined"));
357 dict.insert(QStringLiteral("haschild"), false);
358 break;
359 case QV4::Value::Null_Type:
360 dict.insert(QStringLiteral("type"), QStringLiteral("object"));
361 dict.insert(QStringLiteral("valueencoded"), QStringLiteral("null"));
362 dict.insert(QStringLiteral("haschild"), false);
363 break;
364 case QV4::Value::Boolean_Type:
365 dict.insert(QStringLiteral("value"), value.booleanValue());
366 dict.insert(QStringLiteral("haschild"), false);
367 break;
368 case QV4::Value::Managed_Type:
369 if (const QV4::String *string = value.as<QV4::String>()) {
370 dict.insert(QStringLiteral("value"), string->toQStringNoThrow());
371 dict.insert(QStringLiteral("haschild"), false);
372 dict.insert(QStringLiteral("valueencoded"), QStringLiteral("utf16"));
373 dict.insert(QStringLiteral("quoted"), true);
374 } else if (const QV4::ArrayObject *array = value.as<QV4::ArrayObject>()) {
375 // The size of an array is number of its numerical properties.
376 // We don't consider free form object properties here.
377 const uint n = array->getLength();
378 dict.insert(QStringLiteral("value"), qint64(n));
379 dict.insert(QStringLiteral("valueencoded"), QStringLiteral("itemcount"));
380 dict.insert(QStringLiteral("haschild"), qint64(n));
381 if (isExpanded(iname)) {
382 QJsonArray children;
383 for (uint i = 0; i < n; ++i) {
384 QV4::ReturnedValue v = array->get(i);
385 QV4::ScopedValue sval(scope, v);
386 collect(&children, iname, QString::number(i), *sval);
387 }
388 dict.insert(QStringLiteral("children"), children);
389 }
390 } else if (const QV4::Object *object = value.as<QV4::Object>()) {
391 QJsonArray children;
392 bool expanded = isExpanded(iname);
393 qint64 numProperties = 0;
394 QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
395 QV4::ScopedProperty p(scope);
396 QV4::ScopedPropertyKey name(scope);
397 while (true) {
398 QV4::PropertyAttributes attrs;
399 name = it.next(p, &attrs);
400 if (!name->isValid())
401 break;
402 if (name->isStringOrSymbol()) {
403 ++numProperties;
404 if (expanded) {
405 QV4::Value v = p.property->value;
406 collect(&children, iname, name->toQString(), v);
407 }
408 }
409 }
410 dict.insert(QStringLiteral("value"), numProperties);
411 dict.insert(QStringLiteral("valueencoded"), QStringLiteral("itemcount"));
412 dict.insert(QStringLiteral("haschild"), numProperties > 0);
413 if (expanded)
414 dict.insert(QStringLiteral("children"), children);
415 }
416 break;
417 case QV4::Value::Integer_Type:
418 dict.insert(QStringLiteral("value"), value.integerValue());
419 dict.insert(QStringLiteral("haschild"), false);
420 break;
421 default: // double
422 dict.insert(QStringLiteral("value"), value.doubleValue());
423 dict.insert(QStringLiteral("haschild"), false);
424 }
425
426 out->append(dict);
427}
428
429void NativeDebugger::handleVariables(QJsonObject *response, const QJsonObject &arguments)
430{
431 TRACE_PROTOCOL("Build variables");
432 QV4::CppStackFrame *frame = nullptr;
433 decodeFrame(arguments.value(QLatin1String("context")).toString(), &frame);
434 if (!frame) {
435 setError(response, QStringLiteral("No stack frame passed"));
436 return;
437 }
438 TRACE_PROTOCOL("Context: " << frame);
439
440 QV4::ExecutionEngine *engine = frame->v4Function->internalClass->engine;
441 if (!engine) {
442 setError(response, QStringLiteral("No execution engine passed"));
443 return;
444 }
445 TRACE_PROTOCOL("Engine: " << engine);
446
447 Collector collector(engine);
448 const QJsonArray expanded = arguments.value(QLatin1String("expanded")).toArray();
449 for (const QJsonValue ex : expanded)
450 collector.m_expanded.append(ex.toString());
451 TRACE_PROTOCOL("Expanded: " << collector.m_expanded);
452
453 QJsonArray output;
454 QV4::Scope scope(engine);
455
456 QV4::ScopedValue thisObject(scope, frame->thisObject());
457 collector.collect(&output, QString(), QStringLiteral("this"), thisObject);
458 QV4::Scoped<QV4::CallContext> callContext(scope, frame->callContext());
459 if (callContext) {
460 QV4::Heap::InternalClass *ic = callContext->internalClass();
461 QV4::ScopedValue v(scope);
462 for (uint i = 0; i < ic->size; ++i) {
463 QV4::ScopedValue stringOrSymbol(scope, ic->keyAt(i));
464 QV4::ScopedString propName(scope, stringOrSymbol->toString(scope.engine));
465 v = callContext->getProperty(propName);
466 collector.collect(&output, QString(), propName->toQString(), v);
467 }
468 }
469
470 response->insert(QStringLiteral("variables"), output);
471}
472
473void NativeDebugger::handleExpressions(QJsonObject *response, const QJsonObject &arguments)
474{
475 TRACE_PROTOCOL("Evaluate expressions");
476 QV4::CppStackFrame *frame = nullptr;
477 decodeFrame(arguments.value(QLatin1String("context")).toString(), &frame);
478 if (!frame) {
479 setError(response, QStringLiteral("No stack frame passed"));
480 return;
481 }
482 TRACE_PROTOCOL("Context: " << executionContext);
483
484 QV4::ExecutionEngine *engine = frame->v4Function->internalClass->engine;
485 if (!engine) {
486 setError(response, QStringLiteral("No execution engine passed"));
487 return;
488 }
489 TRACE_PROTOCOL("Engines: " << engine << m_engine);
490
491 Collector collector(engine);
492 const QJsonArray expanded = arguments.value(QLatin1String("expanded")).toArray();
493 for (const QJsonValue ex : expanded)
494 collector.m_expanded.append(ex.toString());
495 TRACE_PROTOCOL("Expanded: " << collector.m_expanded);
496
497 QJsonArray output;
498 QV4::Scope scope(engine);
499
500 const QJsonArray expressions = arguments.value(QLatin1String("expressions")).toArray();
501 for (const QJsonValue expr : expressions) {
502 QString expression = expr.toObject().value(QLatin1String("expression")).toString();
503 QString name = expr.toObject().value(QLatin1String("name")).toString();
504 TRACE_PROTOCOL("Evaluate expression: " << expression);
505 m_runningJob = true;
506
507 QV4::ScopedValue result(scope, evaluateExpression(expression));
508
509 m_runningJob = false;
510 if (result->isUndefined()) {
511 QJsonObject dict;
512 dict.insert(QStringLiteral("name"), name);
513 dict.insert(QStringLiteral("valueencoded"), QStringLiteral("undefined"));
514 output.append(dict);
515 } else if (result.ptr && result.ptr->rawValue()) {
516 collector.collect(&output, QString(), name, *result);
517 } else {
518 QJsonObject dict;
519 dict.insert(QStringLiteral("name"), name);
520 dict.insert(QStringLiteral("valueencoded"), QStringLiteral("notaccessible"));
521 output.append(dict);
522 }
523 TRACE_PROTOCOL("EXCEPTION: " << engine->hasException);
524 engine->hasException = false;
525 }
526
527 response->insert(QStringLiteral("expressions"), output);
528}
529
531{
532 for (int i = 0; i != m_breakPoints.size(); ++i) {
533 if (m_breakPoints.at(i).id == id) {
534 m_breakPoints.remove(i);
535 m_haveBreakPoints = !m_breakPoints.isEmpty();
536 return;
537 }
538 }
539}
540
541void BreakPointHandler::enableBreakPoint(int id, bool enabled)
542{
543 m_breakPoints[id].enabled = enabled;
544}
545
546void NativeDebugger::pause()
547{
548 m_pauseRequested = true;
549}
550
551void NativeDebugger::handleContinue(QJsonObject *response, Speed speed)
552{
553 Q_UNUSED(response);
554
555 if (!m_returnedValue.isUndefined())
556 m_returnedValue.set(m_engine, QV4::Encode::undefined());
557
558 m_currentFrame = m_engine->currentStackFrame;
559 m_stepping = speed;
560}
561
563{
564 if (m_runningJob) // do not re-enter when we're doing a job for the debugger.
565 return;
566
567 if (m_stepping == StepOver) {
568 if (m_currentFrame == m_engine->currentStackFrame)
569 pauseAndWait();
570 return;
571 }
572
573 if (m_stepping == StepIn) {
574 pauseAndWait();
575 return;
576 }
577
578 if (m_pauseRequested) { // Serve debugging requests from the agent
579 m_pauseRequested = false;
580 pauseAndWait();
581 return;
582 }
583
584 if (m_service->m_breakHandler->m_haveBreakPoints) {
585 if (QV4::Function *function = getFunction()) {
586 // lineNumber will be negative for Ret instructions, so those won't match
587 const int lineNumber = m_engine->currentStackFrame->lineNumber();
588 if (reallyHitTheBreakPoint(function, lineNumber))
589 pauseAndWait();
590 }
591 }
592}
593
595{
596 if (m_runningJob)
597 return;
598
599 if (m_stepping == StepIn) {
600 m_currentFrame = m_engine->currentStackFrame;
601 }
602}
603
604void NativeDebugger::leavingFunction(const QV4::ReturnedValue &retVal)
605{
606 if (m_runningJob)
607 return;
608
609 if (m_stepping != NotStepping && m_currentFrame == m_engine->currentStackFrame) {
610 m_currentFrame = m_currentFrame->parentFrame();
611 m_stepping = StepOver;
612 m_returnedValue.set(m_engine, retVal);
613 }
614}
615
617{
618 if (!m_service->m_breakHandler->m_breakOnThrow)
619 return;
620
621 if (m_runningJob) // do not re-enter when we're doing a job for the debugger.
622 return;
623
624 QJsonObject event;
625 // TODO: complete this!
626 event.insert(QStringLiteral("event"), QStringLiteral("exception"));
627 m_service->emitAsynchronousMessageToClient(event);
628}
629
630QV4::Function *NativeDebugger::getFunction() const
631{
632 if (m_engine->currentStackFrame)
633 return m_engine->currentStackFrame->v4Function;
634 else
635 return m_engine->globalCode;
636}
637
638void NativeDebugger::pauseAndWait()
639{
640 QJsonObject event;
641
642 event.insert(QStringLiteral("event"), QStringLiteral("break"));
643 event.insert(QStringLiteral("language"), QStringLiteral("js"));
644 if (QV4::CppStackFrame *frame = m_engine->currentStackFrame) {
645 QV4::Function *function = frame->v4Function;
646 event.insert(QStringLiteral("file"), function->sourceFile());
647 int line = frame->lineNumber();
648 event.insert(QStringLiteral("line"), (line < 0 ? -line : line));
649 }
650
651 m_service->emitAsynchronousMessageToClient(event);
652}
653
654bool NativeDebugger::reallyHitTheBreakPoint(const QV4::Function *function, int lineNumber)
655{
656 for (int i = 0, n = m_service->m_breakHandler->m_breakPoints.size(); i != n; ++i) {
657 const BreakPoint &bp = m_service->m_breakHandler->m_breakPoints.at(i);
658 if (bp.lineNumber == lineNumber) {
659 const QString base = QUrl(function->sourceFile()).fileName();
660 if (bp.fileName.endsWith(base)) {
661 if (bp.condition.isEmpty() || checkCondition(bp.condition)) {
662 BreakPoint &mbp = m_service->m_breakHandler->m_breakPoints[i];
663 ++mbp.hitCount;
664 if (mbp.hitCount > mbp.ignoreCount)
665 return true;
666 }
667 }
668 }
669 }
670 return false;
671}
672
675{
676 m_breakHandler = new BreakPointHandler;
677}
678
680{
681 delete m_breakHandler;
682}
683
685{
686 TRACE_PROTOCOL("Adding engine" << engine);
687 if (engine) {
688 QV4::ExecutionEngine *ee = engine->handle();
689 TRACE_PROTOCOL("Adding execution engine" << ee);
690 if (ee) {
691 NativeDebugger *debugger = new NativeDebugger(this, ee);
692 if (state() == Enabled)
693 ee->setDebugger(debugger);
694 m_debuggers.append(QPointer<NativeDebugger>(debugger));
695 }
696 }
697 QQmlDebugService::engineAboutToBeAdded(engine);
698}
699
701{
702 TRACE_PROTOCOL("Removing engine" << engine);
703 if (engine) {
704 QV4::ExecutionEngine *executionEngine = engine->handle();
705 const auto debuggersCopy = m_debuggers;
706 for (NativeDebugger *debugger : debuggersCopy) {
707 if (debugger->engine() == executionEngine)
708 m_debuggers.removeAll(debugger);
709 }
710 }
711 QQmlDebugService::engineAboutToBeRemoved(engine);
712}
713
714void QQmlNativeDebugServiceImpl::stateAboutToBeChanged(QQmlDebugService::State state)
715{
716 if (state == Enabled) {
717 for (NativeDebugger *debugger : std::as_const(m_debuggers)) {
718 QV4::ExecutionEngine *engine = debugger->engine();
719 if (!engine->debugger())
720 engine->setDebugger(debugger);
721 }
722 }
723 QQmlDebugService::stateAboutToBeChanged(state);
724}
725
726void QQmlNativeDebugServiceImpl::messageReceived(const QByteArray &message)
727{
728 TRACE_PROTOCOL("Native message received: " << message);
729 QJsonObject request = QJsonDocument::fromJson(message).object();
730 QJsonObject response;
731 QJsonObject arguments = request.value(QLatin1String("arguments")).toObject();
732 QString cmd = request.value(QLatin1String("command")).toString();
733
734 if (cmd == QLatin1String("setbreakpoint")) {
735 m_breakHandler->handleSetBreakpoint(&response, arguments);
736 } else if (cmd == QLatin1String("removebreakpoint")) {
737 m_breakHandler->handleRemoveBreakpoint(&response, arguments);
738 } else if (cmd == QLatin1String("echo")) {
739 response.insert(QStringLiteral("result"), arguments);
740 } else {
741 for (NativeDebugger *debugger : std::as_const(m_debuggers))
742 if (debugger)
743 debugger->handleCommand(&response, cmd, arguments);
744 }
745 QJsonDocument doc;
746 doc.setObject(response);
747 QByteArray ba = doc.toJson(QJsonDocument::Compact);
748 TRACE_PROTOCOL("Sending synchronous response:" << ba.constData() << endl);
749 emit messageToClient(s_key, ba);
750}
751
753{
754 QJsonDocument doc;
755 doc.setObject(message);
756 QByteArray ba = doc.toJson(QJsonDocument::Compact);
757 TRACE_PROTOCOL("Sending asynchronous message:" << ba.constData() << endl);
758 emit messageToClient(s_key, ba);
759}
760
761QT_END_NAMESPACE
void setBreakOnThrow(bool onoff)
void enableBreakPoint(int id, bool onoff)
QVector< BreakPoint > m_breakPoints
void handleRemoveBreakpoint(QJsonObject *response, const QJsonObject &arguments)
void handleSetBreakpoint(QJsonObject *response, const QJsonObject &arguments)
Collector(QV4::ExecutionEngine *engine)
void collect(QJsonArray *output, const QString &parentIName, const QString &name, const QV4::Value &value)
QV4::ExecutionEngine * m_engine
bool isExpanded(const QString &iname) const
void maybeBreakAtInstruction() override
void aboutToThrow() override
void handleCommand(QJsonObject *response, const QString &cmd, const QJsonObject &arguments)
QV4::ExecutionEngine * engine() const
NativeDebugger(QQmlNativeDebugServiceImpl *service, QV4::ExecutionEngine *engine)
void leavingFunction(const QV4::ReturnedValue &retVal) override
void enteringFunction() override
bool pauseAtNextOpportunity() const override
void signalEmitted(const QString &signal)
void stateAboutToBeChanged(State state) override
void engineAboutToBeRemoved(QJSEngine *engine) override
void engineAboutToBeAdded(QJSEngine *engine) override
void emitAsynchronousMessageToClient(const QJsonObject &message)
void messageReceived(const QByteArray &message) override
Combined button and popup list for selecting options.
static void setError(QJsonObject *response, const QString &msg)
static QString encodeFrame(QV4::CppStackFrame *f)
size_t qHash(const BreakPoint &b, size_t seed=0) noexcept
bool operator==(const BreakPoint &a, const BreakPoint &b)
static void decodeFrame(const QString &f, QV4::CppStackFrame **frame)
#define TRACE_PROTOCOL(x)