7#include <private/qqmldebugconnector_p.h>
8#include <private/qqmldebugserviceinterfaces_p.h>
9#include <private/qv4debugging_p.h>
10#include <private/qv4debugging_p.h>
11#include <private/qv4engine_p.h>
12#include <private/qv4identifierhash_p.h>
13#include <private/qv4identifiertable_p.h>
14#include <private/qv4objectiterator_p.h>
15#include <private/qv4runtime_p.h>
16#include <private/qv4script_p.h>
17#include <private/qv4stackframe_p.h>
18#include <private/qv4string_p.h>
19#include <private/qversionedpacket_p.h>
21#include <QtQml/qjsengine.h>
22#include <QtCore/qjsonarray.h>
23#include <QtCore/qjsondocument.h>
24#include <QtCore/qjsonobject.h>
25#include <QtCore/qjsonvalue.h>
26#include <QtCore/qvector.h>
27#include <QtCore/qpointer.h>
30#define TRACE_PROTOCOL(s)
34using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
40 bool isValid()
const {
return lineNumber >= 0 && !fileName.isEmpty(); }
54 return qHash(b.fileName, seed) ^ b.lineNumber;
64static void setError(QJsonObject *response,
const QString &msg)
66 response->insert(QStringLiteral(
"type"), QStringLiteral(
"error"));
67 response->insert(QStringLiteral(
"msg"), msg);
79 void collect(QJsonArray *output,
const QString &parentIName,
const QString &name,
80 const QV4::Value &value);
82 bool isExpanded(
const QString &iname)
const {
return m_expanded.contains(iname); }
113 QString type = arguments.value(QLatin1String(
"type")).toString();
115 QString fileName = arguments.value(QLatin1String(
"file")).toString();
116 if (fileName.isEmpty()) {
117 setError(response, QStringLiteral(
"breakpoint has no file name"));
121 int line = arguments.value(QLatin1String(
"line")).toInt(-1);
123 setError(response, QStringLiteral(
"breakpoint has an invalid line number"));
129 bp.fileName = fileName.mid(fileName.lastIndexOf(
'/') + 1);
131 bp
.enabled = arguments.value(QLatin1String(
"enabled")).toBool(
true);
132 bp.condition = arguments.value(QLatin1String(
"condition")).toString();
133 bp
.ignoreCount = arguments.value(QLatin1String(
"ignorecount")).toInt();
134 m_breakPoints.append(bp);
138 response->insert(QStringLiteral(
"type"), type);
139 response->insert(QStringLiteral(
"breakpoint"), bp
.id);
144 int id = arguments.value(QLatin1String(
"id")).toInt();
146 response->insert(QStringLiteral(
"id"), id);
159 return m_pauseRequested
161 || m_stepping >= StepOver;
169 void handleCommand(QJsonObject *response,
const QString &cmd,
const QJsonObject &arguments);
172 void handleBacktrace(QJsonObject *response,
const QJsonObject &arguments);
173 void handleVariables(QJsonObject *response,
const QJsonObject &arguments);
174 void handleExpressions(QJsonObject *response,
const QJsonObject &arguments);
176 void handleDebuggerDeleted(QObject *debugger);
178 QV4::ReturnedValue evaluateExpression(
const QString &expression);
179 bool checkCondition(
const QString &expression);
181 QStringList breakOnSignals;
192 void handleContinue(QJsonObject *reponse, Speed speed);
194 QV4::Function *getFunction()
const;
196 bool reallyHitTheBreakPoint(
const QV4::Function *function,
int lineNumber);
198 QV4::ExecutionEngine *m_engine;
200 QV4::CppStackFrame *m_currentFrame =
nullptr;
202 bool m_pauseRequested;
205 QV4::PersistentValue m_returnedValue;
210 QV4::Scope scope(m_engine);
211 QV4::ScopedValue r(scope, evaluateExpression(expression));
212 return r->booleanValue();
215QV4::ReturnedValue
NativeDebugger::evaluateExpression(
const QString &expression)
217 QV4::Scope scope(m_engine);
220 QV4::ExecutionContext *ctx = m_engine->currentStackFrame ? m_engine->currentContext()
221 : m_engine->scriptContext();
223 QV4::Script script(ctx, QV4::Compiler::ContextType::Eval, expression);
224 if (
const QV4::Function *function = m_engine->currentStackFrame
225 ? m_engine->currentStackFrame->v4Function : m_engine->globalCode) {
226 script.setStrictMode(function->isStrict());
230 script.setInheritContext();
232 if (!m_engine->hasException) {
233 if (m_engine->currentStackFrame) {
234 QV4::ScopedValue thisObject(scope, m_engine->currentStackFrame->thisObject());
235 script.run(thisObject);
241 m_runningJob =
false;
242 return QV4::Encode::undefined();
248 m_stepping = NotStepping;
249 m_pauseRequested =
false;
250 m_runningJob =
false;
264 QString signalName = signal.left(signal.indexOf(QLatin1Char(
'('))).toLower();
266 for (
const QString &signal : std::as_const(breakOnSignals)) {
267 if (signal == signalName) {
275 const QJsonObject &arguments)
277 if (cmd == QLatin1String(
"backtrace"))
278 handleBacktrace(response, arguments);
279 else if (cmd == QLatin1String(
"variables"))
280 handleVariables(response, arguments);
281 else if (cmd == QLatin1String(
"expressions"))
282 handleExpressions(response, arguments);
283 else if (cmd == QLatin1String(
"stepin"))
284 handleContinue(response, StepIn);
285 else if (cmd == QLatin1String(
"stepout"))
286 handleContinue(response, StepOut);
287 else if (cmd == QLatin1String(
"stepover"))
288 handleContinue(response, StepOver);
289 else if (cmd == QLatin1String(
"continue"))
290 handleContinue(response, NotStepping);
297 return QString::fromLatin1(ds.data().toHex());
300static void decodeFrame(
const QString &f, QV4::CppStackFrame **frame)
303 QQmlDebugPacket ds(QByteArray::fromHex(f.toLatin1()));
305 *frame =
reinterpret_cast<QV4::CppStackFrame *>(rawFrame);
308void NativeDebugger::handleBacktrace(QJsonObject *response,
const QJsonObject &arguments)
310 int limit = arguments.value(QLatin1String(
"limit")).toInt(0);
312 QJsonArray frameArray;
313 QV4::CppStackFrame *f= m_engine->currentStackFrame;
314 for (
int i = 0; i < limit && f; ++i) {
315 QV4::Function *function = f->v4Function;
318 frame.insert(QStringLiteral(
"language"), QStringLiteral(
"js"));
319 frame.insert(QStringLiteral(
"context"), encodeFrame(f));
321 if (QV4::Heap::String *functionName = function->name())
322 frame.insert(QStringLiteral(
"function"), functionName->toQString());
323 frame.insert(QStringLiteral(
"file"), function->sourceFile());
325 int line = f->lineNumber();
326 frame.insert(QStringLiteral(
"line"), (line < 0 ? -line : line));
328 frameArray.push_back(frame);
330 f = f->parentFrame();
333 response->insert(QStringLiteral(
"frames"), frameArray);
337 const QV4::Value &value)
340 QV4::Scope scope(m_engine);
342 QString nonEmptyName = name.isEmpty() ? QString::fromLatin1(
"@%1").arg(m_anonCount++) : name;
343 QString iname = parentIName + QLatin1Char(
'.') + nonEmptyName;
344 dict.insert(QStringLiteral(
"iname"), iname);
345 dict.insert(QStringLiteral(
"name"), nonEmptyName);
347 QV4::ScopedValue typeString(scope, QV4::Runtime::TypeofValue::call(m_engine, value));
348 dict.insert(QStringLiteral(
"type"), typeString->toQStringNoThrow());
350 switch (value.type()) {
351 case QV4::Value::Empty_Type:
352 dict.insert(QStringLiteral(
"valueencoded"), QStringLiteral(
"empty"));
353 dict.insert(QStringLiteral(
"haschild"),
false);
355 case QV4::Value::Undefined_Type:
356 dict.insert(QStringLiteral(
"valueencoded"), QStringLiteral(
"undefined"));
357 dict.insert(QStringLiteral(
"haschild"),
false);
359 case QV4::Value::Null_Type:
360 dict.insert(QStringLiteral(
"type"), QStringLiteral(
"object"));
361 dict.insert(QStringLiteral(
"valueencoded"), QStringLiteral(
"null"));
362 dict.insert(QStringLiteral(
"haschild"),
false);
364 case QV4::Value::Boolean_Type:
365 dict.insert(QStringLiteral(
"value"), value.booleanValue());
366 dict.insert(QStringLiteral(
"haschild"),
false);
368 case QV4::Value::Managed_Type:
369 if (
const QV4::String *string = value.as<QV4::String>()) {
370 dict.insert(QStringLiteral(
"value"), string->toQStringNoThrow());
371 dict.insert(QStringLiteral(
"haschild"),
false);
372 dict.insert(QStringLiteral(
"valueencoded"), QStringLiteral(
"utf16"));
373 dict.insert(QStringLiteral(
"quoted"),
true);
374 }
else if (
const QV4::ArrayObject *array = value.as<QV4::ArrayObject>()) {
377 const uint n = array->getLength();
378 dict.insert(QStringLiteral(
"value"), qint64(n));
379 dict.insert(QStringLiteral(
"valueencoded"), QStringLiteral(
"itemcount"));
380 dict.insert(QStringLiteral(
"haschild"), qint64(n));
381 if (isExpanded(iname)) {
383 for (uint i = 0; i < n; ++i) {
384 QV4::ReturnedValue v = array->get(i);
385 QV4::ScopedValue sval(scope, v);
386 collect(&children, iname, QString::number(i), *sval);
388 dict.insert(QStringLiteral(
"children"), children);
390 }
else if (
const QV4::Object *object = value.as<QV4::Object>()) {
392 bool expanded = isExpanded(iname);
393 qint64 numProperties = 0;
394 QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
395 QV4::ScopedProperty p(scope);
396 QV4::ScopedPropertyKey name(scope);
398 QV4::PropertyAttributes attrs;
399 name = it.next(p, &attrs);
400 if (!name->isValid())
402 if (name->isStringOrSymbol()) {
405 QV4::Value v = p.property->value;
406 collect(&children, iname, name->toQString(), v);
410 dict.insert(QStringLiteral(
"value"), numProperties);
411 dict.insert(QStringLiteral(
"valueencoded"), QStringLiteral(
"itemcount"));
412 dict.insert(QStringLiteral(
"haschild"), numProperties > 0);
414 dict.insert(QStringLiteral(
"children"), children);
417 case QV4::Value::Integer_Type:
418 dict.insert(QStringLiteral(
"value"), value.integerValue());
419 dict.insert(QStringLiteral(
"haschild"),
false);
422 dict.insert(QStringLiteral(
"value"), value.doubleValue());
423 dict.insert(QStringLiteral(
"haschild"),
false);
429void NativeDebugger::handleVariables(QJsonObject *response,
const QJsonObject &arguments)
432 QV4::CppStackFrame *frame =
nullptr;
433 decodeFrame(arguments.value(QLatin1String(
"context")).toString(), &frame);
435 setError(response, QStringLiteral(
"No stack frame passed"));
440 QV4::ExecutionEngine *engine = frame->v4Function->internalClass->engine;
442 setError(response, QStringLiteral(
"No execution engine passed"));
448 const QJsonArray expanded = arguments.value(QLatin1String(
"expanded")).toArray();
449 for (
const QJsonValue ex : expanded)
450 collector.m_expanded.append(ex.toString());
454 QV4::Scope scope(engine);
456 QV4::ScopedValue thisObject(scope, frame->thisObject());
457 collector.collect(&output, QString(), QStringLiteral(
"this"), thisObject);
458 QV4::Scoped<QV4::CallContext> callContext(scope, frame->callContext());
460 QV4::Heap::InternalClass *ic = callContext->internalClass();
461 QV4::ScopedValue v(scope);
462 for (uint i = 0; i < ic->size; ++i) {
463 QV4::ScopedValue stringOrSymbol(scope, ic->keyAt(i));
464 QV4::ScopedString propName(scope, stringOrSymbol->toString(scope.engine));
465 v = callContext->getProperty(propName);
466 collector.collect(&output, QString(), propName->toQString(), v);
470 response->insert(QStringLiteral(
"variables"), output);
473void NativeDebugger::handleExpressions(QJsonObject *response,
const QJsonObject &arguments)
476 QV4::CppStackFrame *frame =
nullptr;
477 decodeFrame(arguments.value(QLatin1String(
"context")).toString(), &frame);
479 setError(response, QStringLiteral(
"No stack frame passed"));
484 QV4::ExecutionEngine *engine = frame->v4Function->internalClass->engine;
486 setError(response, QStringLiteral(
"No execution engine passed"));
492 const QJsonArray expanded = arguments.value(QLatin1String(
"expanded")).toArray();
493 for (
const QJsonValue ex : expanded)
494 collector.m_expanded.append(ex.toString());
498 QV4::Scope scope(engine);
500 const QJsonArray expressions = arguments.value(QLatin1String(
"expressions")).toArray();
501 for (
const QJsonValue expr : expressions) {
502 QString expression = expr.toObject().value(QLatin1String(
"expression")).toString();
503 QString name = expr.toObject().value(QLatin1String(
"name")).toString();
507 QV4::ScopedValue result(scope, evaluateExpression(expression));
509 m_runningJob =
false;
510 if (result->isUndefined()) {
512 dict.insert(QStringLiteral(
"name"), name);
513 dict.insert(QStringLiteral(
"valueencoded"), QStringLiteral(
"undefined"));
515 }
else if (result.ptr && result.ptr->rawValue()) {
516 collector.collect(&output, QString(), name, *result);
519 dict.insert(QStringLiteral(
"name"), name);
520 dict.insert(QStringLiteral(
"valueencoded"), QStringLiteral(
"notaccessible"));
524 engine->hasException =
false;
527 response->insert(QStringLiteral(
"expressions"), output);
532 for (
int i = 0; i != m_breakPoints.size(); ++i) {
533 if (m_breakPoints.at(i).id == id) {
534 m_breakPoints.remove(i);
535 m_haveBreakPoints = !m_breakPoints.isEmpty();
543 m_breakPoints[id].enabled = enabled;
548 m_pauseRequested =
true;
551void NativeDebugger::handleContinue(QJsonObject *response, Speed speed)
555 if (!m_returnedValue.isUndefined())
556 m_returnedValue.set(m_engine, QV4::Encode::undefined());
558 m_currentFrame = m_engine->currentStackFrame;
567 if (m_stepping == StepOver) {
568 if (m_currentFrame == m_engine->currentStackFrame)
573 if (m_stepping == StepIn) {
578 if (m_pauseRequested) {
579 m_pauseRequested =
false;
585 if (QV4::Function *function = getFunction()) {
587 const int lineNumber = m_engine->currentStackFrame->lineNumber();
588 if (reallyHitTheBreakPoint(function, lineNumber))
599 if (m_stepping == StepIn) {
600 m_currentFrame = m_engine->currentStackFrame;
609 if (m_stepping != NotStepping && m_currentFrame == m_engine->currentStackFrame) {
610 m_currentFrame = m_currentFrame->parentFrame();
611 m_stepping = StepOver;
612 m_returnedValue.set(m_engine, retVal);
626 event.insert(QStringLiteral(
"event"), QStringLiteral(
"exception"));
627 m_service->emitAsynchronousMessageToClient(event);
632 if (m_engine->currentStackFrame)
633 return m_engine->currentStackFrame->v4Function;
635 return m_engine->globalCode;
642 event.insert(QStringLiteral(
"event"), QStringLiteral(
"break"));
643 event.insert(QStringLiteral(
"language"), QStringLiteral(
"js"));
644 if (QV4::CppStackFrame *frame = m_engine->currentStackFrame) {
645 QV4::Function *function = frame->v4Function;
646 event.insert(QStringLiteral(
"file"), function->sourceFile());
647 int line = frame->lineNumber();
648 event.insert(QStringLiteral(
"line"), (line < 0 ? -line : line));
651 m_service->emitAsynchronousMessageToClient(event);
654bool NativeDebugger::reallyHitTheBreakPoint(
const QV4::Function *function,
int lineNumber)
656 for (
int i = 0, n = m_service->m_breakHandler->m_breakPoints.size(); i != n; ++i) {
657 const BreakPoint &bp = m_service->m_breakHandler->m_breakPoints.at(i);
659 const QString base = QUrl(function->sourceFile()).fileName();
660 if (bp.fileName.endsWith(base)) {
661 if (bp.condition.isEmpty() || checkCondition(bp.condition)) {
662 BreakPoint &mbp = m_service->m_breakHandler->m_breakPoints[i];
681 delete m_breakHandler;
688 QV4::ExecutionEngine *ee = engine->handle();
692 if (state() == Enabled)
693 ee->setDebugger(debugger);
694 m_debuggers.append(QPointer<NativeDebugger>(debugger));
697 QQmlDebugService::engineAboutToBeAdded(engine);
704 QV4::ExecutionEngine *executionEngine = engine->handle();
705 const auto debuggersCopy = m_debuggers;
706 for (NativeDebugger *debugger : debuggersCopy) {
707 if (debugger->engine() == executionEngine)
708 m_debuggers.removeAll(debugger);
711 QQmlDebugService::engineAboutToBeRemoved(engine);
716 if (state == Enabled) {
717 for (NativeDebugger *debugger : std::as_const(m_debuggers)) {
718 QV4::ExecutionEngine *engine = debugger->engine();
719 if (!engine->debugger())
720 engine->setDebugger(debugger);
723 QQmlDebugService::stateAboutToBeChanged(state);
729 QJsonObject request = QJsonDocument::fromJson(message).object();
730 QJsonObject response;
731 QJsonObject arguments = request.value(QLatin1String(
"arguments")).toObject();
732 QString cmd = request.value(QLatin1String(
"command")).toString();
734 if (cmd == QLatin1String(
"setbreakpoint")) {
735 m_breakHandler->handleSetBreakpoint(&response, arguments);
736 }
else if (cmd == QLatin1String(
"removebreakpoint")) {
737 m_breakHandler->handleRemoveBreakpoint(&response, arguments);
738 }
else if (cmd == QLatin1String(
"echo")) {
739 response.insert(QStringLiteral(
"result"), arguments);
741 for (NativeDebugger *debugger : std::as_const(m_debuggers))
743 debugger->handleCommand(&response, cmd, arguments);
746 doc.setObject(response);
747 QByteArray ba = doc.toJson(QJsonDocument::Compact);
748 TRACE_PROTOCOL(
"Sending synchronous response:" << ba.constData() << endl);
749 emit messageToClient(s_key, ba);
755 doc.setObject(message);
756 QByteArray ba = doc.toJson(QJsonDocument::Compact);
757 TRACE_PROTOCOL(
"Sending asynchronous message:" << ba.constData() << endl);
758 emit messageToClient(s_key, ba);
void setBreakOnThrow(bool onoff)
void removeBreakPoint(int id)
void enableBreakPoint(int id, bool onoff)
QVector< BreakPoint > m_breakPoints
void handleRemoveBreakpoint(QJsonObject *response, const QJsonObject &arguments)
void handleSetBreakpoint(QJsonObject *response, const QJsonObject &arguments)
Collector(QV4::ExecutionEngine *engine)
void collect(QJsonArray *output, const QString &parentIName, const QString &name, const QV4::Value &value)
QV4::ExecutionEngine * m_engine
bool isExpanded(const QString &iname) const
void maybeBreakAtInstruction() override
void aboutToThrow() override
void handleCommand(QJsonObject *response, const QString &cmd, const QJsonObject &arguments)
QV4::ExecutionEngine * engine() const
NativeDebugger(QQmlNativeDebugServiceImpl *service, QV4::ExecutionEngine *engine)
void leavingFunction(const QV4::ReturnedValue &retVal) override
void enteringFunction() override
bool pauseAtNextOpportunity() const override
void signalEmitted(const QString &signal)
void stateAboutToBeChanged(State state) override
void engineAboutToBeRemoved(QJSEngine *engine) override
void engineAboutToBeAdded(QJSEngine *engine) override
void emitAsynchronousMessageToClient(const QJsonObject &message)
QQmlNativeDebugServiceImpl(QObject *parent)
void messageReceived(const QByteArray &message) override
~QQmlNativeDebugServiceImpl() override
Combined button and popup list for selecting options.
static void setError(QJsonObject *response, const QString &msg)
static QString encodeFrame(QV4::CppStackFrame *f)
size_t qHash(const BreakPoint &b, size_t seed=0) noexcept
bool operator==(const BreakPoint &a, const BreakPoint &b)
static void decodeFrame(const QString &f, QV4::CppStackFrame **frame)
#define TRACE_PROTOCOL(x)