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

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