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(ch: 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 QV4::ScopedValue stringOrSymbol(scope, ic->keyAt(index: i));
461 QV4::ScopedString propName(scope, stringOrSymbol->toString(e: scope.engine));
462 v = callContext->getProperty(name: propName);
463 collector.collect(out: &output, parentIName: QString(), name: propName->toQString(), value: v);
464 }
465 }
466
467 response->insert(QStringLiteral("variables"), value: output);
468}
469
470void NativeDebugger::handleExpressions(QJsonObject *response, const QJsonObject &arguments)
471{
472 TRACE_PROTOCOL("Evaluate expressions");
473 QV4::CppStackFrame *frame = nullptr;
474 decodeFrame(f: arguments.value(key: QLatin1String("context")).toString(), frame: &frame);
475 if (!frame) {
476 setError(response, QStringLiteral("No stack frame passed"));
477 return;
478 }
479 TRACE_PROTOCOL("Context: " << executionContext);
480
481 QV4::ExecutionEngine *engine = frame->v4Function->internalClass->engine;
482 if (!engine) {
483 setError(response, QStringLiteral("No execution engine passed"));
484 return;
485 }
486 TRACE_PROTOCOL("Engines: " << engine << m_engine);
487
488 Collector collector(engine);
489 const QJsonArray expanded = arguments.value(key: QLatin1String("expanded")).toArray();
490 for (const QJsonValue ex : expanded)
491 collector.m_expanded.append(t: ex.toString());
492 TRACE_PROTOCOL("Expanded: " << collector.m_expanded);
493
494 QJsonArray output;
495 QV4::Scope scope(engine);
496
497 const QJsonArray expressions = arguments.value(key: QLatin1String("expressions")).toArray();
498 for (const QJsonValue expr : expressions) {
499 QString expression = expr.toObject().value(key: QLatin1String("expression")).toString();
500 QString name = expr.toObject().value(key: QLatin1String("name")).toString();
501 TRACE_PROTOCOL("Evaluate expression: " << expression);
502 m_runningJob = true;
503
504 QV4::ScopedValue result(scope, evaluateExpression(expression));
505
506 m_runningJob = false;
507 if (result->isUndefined()) {
508 QJsonObject dict;
509 dict.insert(QStringLiteral("name"), value: name);
510 dict.insert(QStringLiteral("valueencoded"), QStringLiteral("undefined"));
511 output.append(value: dict);
512 } else if (result.ptr && result.ptr->rawValue()) {
513 collector.collect(out: &output, parentIName: QString(), name, value: *result);
514 } else {
515 QJsonObject dict;
516 dict.insert(QStringLiteral("name"), value: name);
517 dict.insert(QStringLiteral("valueencoded"), QStringLiteral("notaccessible"));
518 output.append(value: dict);
519 }
520 TRACE_PROTOCOL("EXCEPTION: " << engine->hasException);
521 engine->hasException = false;
522 }
523
524 response->insert(QStringLiteral("expressions"), value: output);
525}
526
527void BreakPointHandler::removeBreakPoint(int id)
528{
529 for (int i = 0; i != m_breakPoints.size(); ++i) {
530 if (m_breakPoints.at(i).id == id) {
531 m_breakPoints.remove(i);
532 m_haveBreakPoints = !m_breakPoints.isEmpty();
533 return;
534 }
535 }
536}
537
538void BreakPointHandler::enableBreakPoint(int id, bool enabled)
539{
540 m_breakPoints[id].enabled = enabled;
541}
542
543void NativeDebugger::pause()
544{
545 m_pauseRequested = true;
546}
547
548void NativeDebugger::handleContinue(QJsonObject *response, Speed speed)
549{
550 Q_UNUSED(response);
551
552 if (!m_returnedValue.isUndefined())
553 m_returnedValue.set(engine: m_engine, value: QV4::Encode::undefined());
554
555 m_currentFrame = m_engine->currentStackFrame;
556 m_stepping = speed;
557}
558
559void NativeDebugger::maybeBreakAtInstruction()
560{
561 if (m_runningJob) // do not re-enter when we're doing a job for the debugger.
562 return;
563
564 if (m_stepping == StepOver) {
565 if (m_currentFrame == m_engine->currentStackFrame)
566 pauseAndWait();
567 return;
568 }
569
570 if (m_stepping == StepIn) {
571 pauseAndWait();
572 return;
573 }
574
575 if (m_pauseRequested) { // Serve debugging requests from the agent
576 m_pauseRequested = false;
577 pauseAndWait();
578 return;
579 }
580
581 if (m_service->m_breakHandler->m_haveBreakPoints) {
582 if (QV4::Function *function = getFunction()) {
583 // lineNumber will be negative for Ret instructions, so those won't match
584 const int lineNumber = m_engine->currentStackFrame->lineNumber();
585 if (reallyHitTheBreakPoint(function, lineNumber))
586 pauseAndWait();
587 }
588 }
589}
590
591void NativeDebugger::enteringFunction()
592{
593 if (m_runningJob)
594 return;
595
596 if (m_stepping == StepIn) {
597 m_currentFrame = m_engine->currentStackFrame;
598 }
599}
600
601void NativeDebugger::leavingFunction(const QV4::ReturnedValue &retVal)
602{
603 if (m_runningJob)
604 return;
605
606 if (m_stepping != NotStepping && m_currentFrame == m_engine->currentStackFrame) {
607 m_currentFrame = m_currentFrame->parentFrame();
608 m_stepping = StepOver;
609 m_returnedValue.set(engine: m_engine, value: retVal);
610 }
611}
612
613void NativeDebugger::aboutToThrow()
614{
615 if (!m_service->m_breakHandler->m_breakOnThrow)
616 return;
617
618 if (m_runningJob) // do not re-enter when we're doing a job for the debugger.
619 return;
620
621 QJsonObject event;
622 // TODO: complete this!
623 event.insert(QStringLiteral("event"), QStringLiteral("exception"));
624 m_service->emitAsynchronousMessageToClient(message: event);
625}
626
627QV4::Function *NativeDebugger::getFunction() const
628{
629 if (m_engine->currentStackFrame)
630 return m_engine->currentStackFrame->v4Function;
631 else
632 return m_engine->globalCode;
633}
634
635void NativeDebugger::pauseAndWait()
636{
637 QJsonObject event;
638
639 event.insert(QStringLiteral("event"), QStringLiteral("break"));
640 event.insert(QStringLiteral("language"), QStringLiteral("js"));
641 if (QV4::CppStackFrame *frame = m_engine->currentStackFrame) {
642 QV4::Function *function = frame->v4Function;
643 event.insert(QStringLiteral("file"), value: function->sourceFile());
644 int line = frame->lineNumber();
645 event.insert(QStringLiteral("line"), value: (line < 0 ? -line : line));
646 }
647
648 m_service->emitAsynchronousMessageToClient(message: event);
649}
650
651bool NativeDebugger::reallyHitTheBreakPoint(const QV4::Function *function, int lineNumber)
652{
653 for (int i = 0, n = m_service->m_breakHandler->m_breakPoints.size(); i != n; ++i) {
654 const BreakPoint &bp = m_service->m_breakHandler->m_breakPoints.at(i);
655 if (bp.lineNumber == lineNumber) {
656 const QString base = QUrl(function->sourceFile()).fileName();
657 if (bp.fileName.endsWith(s: base)) {
658 if (bp.condition.isEmpty() || checkCondition(expression: bp.condition)) {
659 BreakPoint &mbp = m_service->m_breakHandler->m_breakPoints[i];
660 ++mbp.hitCount;
661 if (mbp.hitCount > mbp.ignoreCount)
662 return true;
663 }
664 }
665 }
666 }
667 return false;
668}
669
670QQmlNativeDebugServiceImpl::QQmlNativeDebugServiceImpl(QObject *parent)
671 : QQmlNativeDebugService(1.0, parent)
672{
673 m_breakHandler = new BreakPointHandler;
674}
675
676QQmlNativeDebugServiceImpl::~QQmlNativeDebugServiceImpl()
677{
678 delete m_breakHandler;
679}
680
681void QQmlNativeDebugServiceImpl::engineAboutToBeAdded(QJSEngine *engine)
682{
683 TRACE_PROTOCOL("Adding engine" << engine);
684 if (engine) {
685 QV4::ExecutionEngine *ee = engine->handle();
686 TRACE_PROTOCOL("Adding execution engine" << ee);
687 if (ee) {
688 NativeDebugger *debugger = new NativeDebugger(this, ee);
689 if (state() == Enabled)
690 ee->setDebugger(debugger);
691 m_debuggers.append(t: QPointer<NativeDebugger>(debugger));
692 }
693 }
694 QQmlDebugService::engineAboutToBeAdded(engine);
695}
696
697void QQmlNativeDebugServiceImpl::engineAboutToBeRemoved(QJSEngine *engine)
698{
699 TRACE_PROTOCOL("Removing engine" << engine);
700 if (engine) {
701 QV4::ExecutionEngine *executionEngine = engine->handle();
702 const auto debuggersCopy = m_debuggers;
703 for (NativeDebugger *debugger : debuggersCopy) {
704 if (debugger->engine() == executionEngine)
705 m_debuggers.removeAll(t: debugger);
706 }
707 }
708 QQmlDebugService::engineAboutToBeRemoved(engine);
709}
710
711void QQmlNativeDebugServiceImpl::stateAboutToBeChanged(QQmlDebugService::State state)
712{
713 if (state == Enabled) {
714 for (NativeDebugger *debugger : std::as_const(t&: m_debuggers)) {
715 QV4::ExecutionEngine *engine = debugger->engine();
716 if (!engine->debugger())
717 engine->setDebugger(debugger);
718 }
719 }
720 QQmlDebugService::stateAboutToBeChanged(state);
721}
722
723void QQmlNativeDebugServiceImpl::messageReceived(const QByteArray &message)
724{
725 TRACE_PROTOCOL("Native message received: " << message);
726 QJsonObject request = QJsonDocument::fromJson(json: message).object();
727 QJsonObject response;
728 QJsonObject arguments = request.value(key: QLatin1String("arguments")).toObject();
729 QString cmd = request.value(key: QLatin1String("command")).toString();
730
731 if (cmd == QLatin1String("setbreakpoint")) {
732 m_breakHandler->handleSetBreakpoint(response: &response, arguments);
733 } else if (cmd == QLatin1String("removebreakpoint")) {
734 m_breakHandler->handleRemoveBreakpoint(response: &response, arguments);
735 } else if (cmd == QLatin1String("echo")) {
736 response.insert(QStringLiteral("result"), value: arguments);
737 } else {
738 for (NativeDebugger *debugger : std::as_const(t&: m_debuggers))
739 if (debugger)
740 debugger->handleCommand(response: &response, cmd, arguments);
741 }
742 QJsonDocument doc;
743 doc.setObject(response);
744 QByteArray ba = doc.toJson(format: QJsonDocument::Compact);
745 TRACE_PROTOCOL("Sending synchronous response:" << ba.constData() << endl);
746 emit messageToClient(name: s_key, message: ba);
747}
748
749void QQmlNativeDebugServiceImpl::emitAsynchronousMessageToClient(const QJsonObject &message)
750{
751 QJsonDocument doc;
752 doc.setObject(message);
753 QByteArray ba = doc.toJson(format: QJsonDocument::Compact);
754 TRACE_PROTOCOL("Sending asynchronous message:" << ba.constData() << endl);
755 emit messageToClient(name: s_key, message: ba);
756}
757
758QT_END_NAMESPACE
759

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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