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

source code of qtdeclarative/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.cpp