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 | |
66 | QT_BEGIN_NAMESPACE |
67 | |
68 | using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>; |
69 | |
70 | class BreakPoint |
71 | { |
72 | public: |
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 | |
86 | inline uint qHash(const BreakPoint &b, uint seed = 0) Q_DECL_NOTHROW |
87 | { |
88 | return qHash(key: b.fileName, seed) ^ b.lineNumber; |
89 | } |
90 | |
91 | inline 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 | |
98 | static void setError(QJsonObject *response, const QString &msg) |
99 | { |
100 | response->insert(QStringLiteral("type" ), QStringLiteral("error" )); |
101 | response->insert(QStringLiteral("msg" ), value: msg); |
102 | } |
103 | |
104 | class NativeDebugger; |
105 | |
106 | class Collector |
107 | { |
108 | public: |
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 | |
118 | public: |
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) |
126 | class BreakPointHandler |
127 | { |
128 | public: |
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 | |
144 | void 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 | |
176 | void 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 | |
183 | class NativeDebugger : public QV4::Debugging::Debugger |
184 | { |
185 | public: |
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 | |
205 | private: |
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 | |
242 | bool 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 | |
249 | QV4::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 | |
278 | NativeDebugger::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 | |
289 | void 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 | |
307 | void 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 | |
326 | static QString encodeFrame(QV4::CppStackFrame *f) |
327 | { |
328 | QQmlDebugPacket ds; |
329 | ds << quintptr(f); |
330 | return QString::fromLatin1(str: ds.data().toHex()); |
331 | } |
332 | |
333 | static 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 | |
341 | void 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 | |
369 | void 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 | |
462 | void 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 | |
505 | void 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 | |
562 | void 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 | |
573 | void BreakPointHandler::enableBreakPoint(int id, bool enabled) |
574 | { |
575 | m_breakPoints[id].enabled = enabled; |
576 | } |
577 | |
578 | void NativeDebugger::pause() |
579 | { |
580 | m_pauseRequested = true; |
581 | } |
582 | |
583 | void 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 | |
594 | void 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 | |
626 | void NativeDebugger::enteringFunction() |
627 | { |
628 | if (m_runningJob) |
629 | return; |
630 | |
631 | if (m_stepping == StepIn) { |
632 | m_currentFrame = m_engine->currentStackFrame; |
633 | } |
634 | } |
635 | |
636 | void 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 | |
648 | void 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 | |
662 | QV4::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 | |
670 | void 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 | |
686 | bool 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 | |
705 | QQmlNativeDebugServiceImpl::QQmlNativeDebugServiceImpl(QObject *parent) |
706 | : QQmlNativeDebugService(1.0, parent) |
707 | { |
708 | m_breakHandler = new BreakPointHandler; |
709 | } |
710 | |
711 | QQmlNativeDebugServiceImpl::~QQmlNativeDebugServiceImpl() |
712 | { |
713 | delete m_breakHandler; |
714 | } |
715 | |
716 | void 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 | |
732 | void 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 | |
746 | void 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 | |
758 | void 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 | |
784 | void 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 | |
793 | QT_END_NAMESPACE |
794 | |