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

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