Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #include "qqmlnativedebugservice.h"
++
++ #include <private/qqmldebugconnector_p.h>
++ #include <private/qqmldebugserviceinterfaces_p.h>
++ #include <private/qv4debugging_p.h>
++ #include <private/qv4debugging_p.h>
++ #include <private/qv4engine_p.h>
++ #include <private/qv4identifierhash_p.h>
++ #include <private/qv4identifiertable_p.h>
++ #include <private/qv4objectiterator_p.h>
++ #include <private/qv4runtime_p.h>
++ #include <private/qv4script_p.h>
++ #include <private/qv4stackframe_p.h>
++ #include <private/qv4string_p.h>
++ #include <private/qversionedpacket_p.h>
++
++ #include <QtQml/qjsengine.h>
++ #include <QtCore/qjsonarray.h>
++ #include <QtCore/qjsondocument.h>
++ #include <QtCore/qjsonobject.h>
++ #include <QtCore/qjsonvalue.h>
++ #include <QtCore/qvector.h>
++ #include <QtCore/qpointer.h>
++
++ //#define TRACE_PROTOCOL(s) qDebug() << s
++ #define TRACE_PROTOCOL(s)
++
++ QT_BEGIN_NAMESPACE
++
++ using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
++
++ class BreakPoint
++ {
++ public:
++ BreakPoint() : id(-1), lineNumber(-1), enabled(false), ignoreCount(0), hitCount(0) {}
++ bool isValid() const { return lineNumber >= 0 && !fileName.isEmpty(); }
++
++ int id;
++ int lineNumber;
++ QString fileName;
++ bool enabled;
++ QString condition;
++ int ignoreCount;
++
++ int hitCount;
++ };
++
++ inline size_t qHash(const BreakPoint &b, size_t seed = 0) noexcept
++ {
++ return qHash(b.fileName, seed) ^ b.lineNumber;
++ }
++
++ inline bool operator==(const BreakPoint &a, const BreakPoint &b)
++ {
++ return a.lineNumber == b.lineNumber && a.fileName == b.fileName
++ && a.enabled == b.enabled && a.condition == b.condition
++ && a.ignoreCount == b.ignoreCount;
++ }
++
++ static void setError(QJsonObject *response, const QString &msg)
++ {
++ response->insert(QStringLiteral("type"), QStringLiteral("error"));
++ response->insert(QStringLiteral("msg"), msg);
++ }
++
++ class NativeDebugger;
++
++ class Collector
++ {
++ public:
++ Collector(QV4::ExecutionEngine *engine)
++ : m_engine(engine), m_anonCount(0)
++ {}
++
++ void collect(QJsonArray *output, const QString &parentIName, const QString &name,
++ const QV4::Value &value);
++
++ bool isExpanded(const QString &iname) const { return m_expanded.contains(iname); }
++
++ public:
++ QV4::ExecutionEngine *m_engine;
++ int m_anonCount;
++ QStringList m_expanded;
++ };
++
++ // Encapsulate Breakpoint handling
++ // Could be made per-NativeDebugger (i.e. per execution engine, if needed)
++ class BreakPointHandler
++ {
++ public:
++ BreakPointHandler() : m_haveBreakPoints(false), m_breakOnThrow(true), m_lastBreakpoint(1) {}
++
++ void handleSetBreakpoint(QJsonObject *response, const QJsonObject &arguments);
++ void handleRemoveBreakpoint(QJsonObject *response, const QJsonObject &arguments);
++
++ void removeBreakPoint(int id);
++ void enableBreakPoint(int id, bool onoff);
++
++ void setBreakOnThrow(bool onoff);
++ bool m_haveBreakPoints;
++ bool m_breakOnThrow;
++ int m_lastBreakpoint;
++ QList<BreakPoint> m_breakPoints;
++ };
++
++ void BreakPointHandler::handleSetBreakpoint(QJsonObject *response, const QJsonObject &arguments)
++ {
++ TRACE_PROTOCOL("SET BREAKPOINT" << arguments);
++ QString type = arguments.value(QLatin1String("type")).toString();
++
++ QString fileName = arguments.value(QLatin1String("file")).toString();
++ if (fileName.isEmpty()) {
++ setError(response, QStringLiteral("breakpoint has no file name"));
++ return;
++ }
++
++ int line = arguments.value(QLatin1String("line")).toInt(-1);
++ if (line < 0) {
++ setError(response, QStringLiteral("breakpoint has an invalid line number"));
++ return;
++ }
++
++ BreakPoint bp;
++ bp.id = m_lastBreakpoint++;
++ bp.fileName = fileName.mid(fileName.lastIndexOf('/') + 1);
++ bp.lineNumber = line;
++ bp.enabled = arguments.value(QLatin1String("enabled")).toBool(true);
++ bp.condition = arguments.value(QLatin1String("condition")).toString();
++ bp.ignoreCount = arguments.value(QLatin1String("ignorecount")).toInt();
++ m_breakPoints.append(bp);
++
++ m_haveBreakPoints = true;
++
++ response->insert(QStringLiteral("type"), type);
++ response->insert(QStringLiteral("breakpoint"), bp.id);
++ }
++
++ void BreakPointHandler::handleRemoveBreakpoint(QJsonObject *response, const QJsonObject &arguments)
++ {
++ int id = arguments.value(QLatin1String("id")).toInt();
++ removeBreakPoint(id);
++ response->insert(QStringLiteral("id"), id);
++ }
++
++ class NativeDebugger : public QV4::Debugging::Debugger
++ {
++ public:
++ NativeDebugger(QQmlNativeDebugServiceImpl *service, QV4::ExecutionEngine *engine);
++
++ void signalEmitted(const QString &signal);
++
++ QV4::ExecutionEngine *engine() const { return m_engine; }
++
++ bool pauseAtNextOpportunity() const override {
++ return m_pauseRequested
++ || m_service->m_breakHandler->m_haveBreakPoints
++ || m_stepping >= StepOver;
++ }
++
++ void maybeBreakAtInstruction() override;
++ void enteringFunction() override;
++ void leavingFunction(const QV4::ReturnedValue &retVal) override;
++ void aboutToThrow() override;
++
++ void handleCommand(QJsonObject *response, const QString &cmd, const QJsonObject &arguments);
++
++ private:
++ void handleBacktrace(QJsonObject *response, const QJsonObject &arguments);
++ void handleVariables(QJsonObject *response, const QJsonObject &arguments);
++ void handleExpressions(QJsonObject *response, const QJsonObject &arguments);
++
++ void handleDebuggerDeleted(QObject *debugger);
++
++ QV4::ReturnedValue evaluateExpression(const QString &expression);
++ bool checkCondition(const QString &expression);
++
++ QStringList breakOnSignals;
++
++ enum Speed {
++ NotStepping = 0,
++ StepOut,
++ StepOver,
++ StepIn,
++ };
++
++ void pauseAndWait();
++ void pause();
++ void handleContinue(QJsonObject *reponse, Speed speed);
++
++ QV4::Function *getFunction() const;
++
++ bool reallyHitTheBreakPoint(const QV4::Function *function, int lineNumber);
++
++ QV4::ExecutionEngine *m_engine;
++ QQmlNativeDebugServiceImpl *m_service;
++ QV4::CppStackFrame *m_currentFrame = nullptr;
++ Speed m_stepping;
++ bool m_pauseRequested;
++ bool m_runningJob;
++
++ QV4::PersistentValue m_returnedValue;
++ };
++
++ bool NativeDebugger::checkCondition(const QString &expression)
++ {
++ QV4::Scope scope(m_engine);
++ QV4::ScopedValue r(scope, evaluateExpression(expression));
++ return r->booleanValue();
++ }
++
++ QV4::ReturnedValue NativeDebugger::evaluateExpression(const QString &expression)
++ {
++ QV4::Scope scope(m_engine);
++ m_runningJob = true;
++
++ QV4::ExecutionContext *ctx = m_engine->currentStackFrame ? m_engine->currentContext()
++ : m_engine->scriptContext();
++
++ QV4::Script script(ctx, QV4::Compiler::ContextType::Eval, expression);
++ if (const QV4::Function *function = m_engine->currentStackFrame
++ ? m_engine->currentStackFrame->v4Function : m_engine->globalCode) {
++ script.setStrictMode(function->isStrict());
++ }
++ // In order for property lookups in QML to work, we need to disable fast v4 lookups.
++ // That is a side-effect of inheritContext.
++ script.setInheritContext();
++ script.parse();
++ if (!m_engine->hasException) {
++ if (m_engine->currentStackFrame) {
++ QV4::ScopedValue thisObject(scope, m_engine->currentStackFrame->thisObject());
++ script.run(thisObject);
++ } else {
++ script.run();
++ }
++ }
++
++ m_runningJob = false;
++ return QV4::Encode::undefined();
++ }
++
++ NativeDebugger::NativeDebugger(QQmlNativeDebugServiceImpl *service, QV4::ExecutionEngine *engine)
++ : m_returnedValue(engine, QV4::Value::undefinedValue())
++ {
++ m_stepping = NotStepping;
++ m_pauseRequested = false;
++ m_runningJob = false;
++ m_service = service;
++ m_engine = engine;
++ TRACE_PROTOCOL("Creating native debugger");
++ }
++
++ void NativeDebugger::signalEmitted(const QString &signal)
++ {
++ //This function is only called by QQmlBoundSignal
++ //only if there is a slot connected to the signal. Hence, there
++ //is no need for additional check.
++
++ //Parse just the name and remove the class info
++ //Normalize to Lower case.
++ QString signalName = signal.left(signal.indexOf(QLatin1Char('('))).toLower();
++
++ for (const QString &signal : std::as_const(breakOnSignals)) {
++ if (signal == signalName) {
++ // TODO: pause debugger
++ break;
++ }
++ }
++ }
++
++ void NativeDebugger::handleCommand(QJsonObject *response, const QString &cmd,
++ const QJsonObject &arguments)
++ {
++ if (cmd == QLatin1String("backtrace"))
++ handleBacktrace(response, arguments);
++ else if (cmd == QLatin1String("variables"))
++ handleVariables(response, arguments);
++ else if (cmd == QLatin1String("expressions"))
++ handleExpressions(response, arguments);
++ else if (cmd == QLatin1String("stepin"))
++ handleContinue(response, StepIn);
++ else if (cmd == QLatin1String("stepout"))
++ handleContinue(response, StepOut);
++ else if (cmd == QLatin1String("stepover"))
++ handleContinue(response, StepOver);
++ else if (cmd == QLatin1String("continue"))
++ handleContinue(response, NotStepping);
++ }
++
++ static QString encodeFrame(QV4::CppStackFrame *f)
++ {
++ QQmlDebugPacket ds;
++ ds << quintptr(f);
++ return QString::fromLatin1(ds.data().toHex());
++ }
++
++ static void decodeFrame(const QString &f, QV4::CppStackFrame **frame)
++ {
++ quintptr rawFrame;
++ QQmlDebugPacket ds(QByteArray::fromHex(f.toLatin1()));
++ ds >> rawFrame;
++ *frame = reinterpret_cast<QV4::CppStackFrame *>(rawFrame);
++ }
++
++ void NativeDebugger::handleBacktrace(QJsonObject *response, const QJsonObject &arguments)
++ {
++ int limit = arguments.value(QLatin1String("limit")).toInt(0);
++
++ QJsonArray frameArray;
++ QV4::CppStackFrame *f= m_engine->currentStackFrame;
++ for (int i = 0; i < limit && f; ++i) {
++ QV4::Function *function = f->v4Function;
++
++ QJsonObject frame;
++ frame.insert(QStringLiteral("language"), QStringLiteral("js"));
++ frame.insert(QStringLiteral("context"), encodeFrame(f));
++
++ if (QV4::Heap::String *functionName = function->name())
++ frame.insert(QStringLiteral("function"), functionName->toQString());
++ frame.insert(QStringLiteral("file"), function->sourceFile());
++
++ int line = f->lineNumber();
++ frame.insert(QStringLiteral("line"), (line < 0 ? -line : line));
++
++ frameArray.push_back(frame);
++
++ f = f->parentFrame();
++ }
++
++ response->insert(QStringLiteral("frames"), frameArray);
++ }
++
++ void Collector::collect(QJsonArray *out, const QString &parentIName, const QString &name,
++ const QV4::Value &value)
++ {
++ QJsonObject dict;
++ QV4::Scope scope(m_engine);
++
++ QString nonEmptyName = name.isEmpty() ? QString::fromLatin1("@%1").arg(m_anonCount++) : name;
++ QString iname = parentIName + QLatin1Char('.') + nonEmptyName;
++ dict.insert(QStringLiteral("iname"), iname);
++ dict.insert(QStringLiteral("name"), nonEmptyName);
++
++ QV4::ScopedValue typeString(scope, QV4::Runtime::TypeofValue::call(m_engine, value));
++ dict.insert(QStringLiteral("type"), typeString->toQStringNoThrow());
++
++ switch (value.type()) {
++ case QV4::Value::Empty_Type:
++ dict.insert(QStringLiteral("valueencoded"), QStringLiteral("empty"));
++ dict.insert(QStringLiteral("haschild"), false);
++ break;
++ case QV4::Value::Undefined_Type:
++ dict.insert(QStringLiteral("valueencoded"), QStringLiteral("undefined"));
++ dict.insert(QStringLiteral("haschild"), false);
++ break;
++ case QV4::Value::Null_Type:
++ dict.insert(QStringLiteral("type"), QStringLiteral("object"));
++ dict.insert(QStringLiteral("valueencoded"), QStringLiteral("null"));
++ dict.insert(QStringLiteral("haschild"), false);
++ break;
++ case QV4::Value::Boolean_Type:
++ dict.insert(QStringLiteral("value"), value.booleanValue());
++ dict.insert(QStringLiteral("haschild"), false);
++ break;
++ case QV4::Value::Managed_Type:
++ if (const QV4::String *string = value.as<QV4::String>()) {
++ dict.insert(QStringLiteral("value"), string->toQStringNoThrow());
++ dict.insert(QStringLiteral("haschild"), false);
++ dict.insert(QStringLiteral("valueencoded"), QStringLiteral("utf16"));
++ dict.insert(QStringLiteral("quoted"), true);
++ } else if (const QV4::ArrayObject *array = value.as<QV4::ArrayObject>()) {
++ // The size of an array is number of its numerical properties.
++ // We don't consider free form object properties here.
++ const uint n = array->getLength();
++ dict.insert(QStringLiteral("value"), qint64(n));
++ dict.insert(QStringLiteral("valueencoded"), QStringLiteral("itemcount"));
++ dict.insert(QStringLiteral("haschild"), qint64(n));
++ if (isExpanded(iname)) {
++ QJsonArray children;
++ for (uint i = 0; i < n; ++i) {
++ QV4::ReturnedValue v = array->get(i);
++ QV4::ScopedValue sval(scope, v);
++ collect(&children, iname, QString::number(i), *sval);
++ }
++ dict.insert(QStringLiteral("children"), children);
++ }
++ } else if (const QV4::Object *object = value.as<QV4::Object>()) {
++ QJsonArray children;
++ bool expanded = isExpanded(iname);
++ qint64 numProperties = 0;
++ QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
++ QV4::ScopedProperty p(scope);
++ QV4::ScopedPropertyKey name(scope);
++ while (true) {
++ QV4::PropertyAttributes attrs;
++ name = it.next(p, &attrs);
++ if (!name->isValid())
++ break;
++ if (name->isStringOrSymbol()) {
++ ++numProperties;
++ if (expanded) {
++ QV4::Value v = p.property->value;
++ collect(&children, iname, name->toQString(), v);
++ }
++ }
++ }
++ dict.insert(QStringLiteral("value"), numProperties);
++ dict.insert(QStringLiteral("valueencoded"), QStringLiteral("itemcount"));
++ dict.insert(QStringLiteral("haschild"), numProperties > 0);
++ if (expanded)
++ dict.insert(QStringLiteral("children"), children);
++ }
++ break;
++ case QV4::Value::Integer_Type:
++ dict.insert(QStringLiteral("value"), value.integerValue());
++ dict.insert(QStringLiteral("haschild"), false);
++ break;
++ default: // double
++ dict.insert(QStringLiteral("value"), value.doubleValue());
++ dict.insert(QStringLiteral("haschild"), false);
++ }
++
++ out->append(dict);
++ }
++
++ void NativeDebugger::handleVariables(QJsonObject *response, const QJsonObject &arguments)
++ {
++ TRACE_PROTOCOL("Build variables");
++ QV4::CppStackFrame *frame = nullptr;
++ decodeFrame(arguments.value(QLatin1String("context")).toString(), &frame);
++ if (!frame) {
++ setError(response, QStringLiteral("No stack frame passed"));
++ return;
++ }
++ TRACE_PROTOCOL("Context: " << frame);
++
++ QV4::ExecutionEngine *engine = frame->v4Function->internalClass->engine;
++ if (!engine) {
++ setError(response, QStringLiteral("No execution engine passed"));
++ return;
++ }
++ TRACE_PROTOCOL("Engine: " << engine);
++
++ Collector collector(engine);
++ const QJsonArray expanded = arguments.value(QLatin1String("expanded")).toArray();
++ for (const QJsonValue ex : expanded)
++ collector.m_expanded.append(ex.toString());
++ TRACE_PROTOCOL("Expanded: " << collector.m_expanded);
++
++ QJsonArray output;
++ QV4::Scope scope(engine);
++
++ QV4::ScopedValue thisObject(scope, frame->thisObject());
++ collector.collect(&output, QString(), QStringLiteral("this"), thisObject);
++ QV4::Scoped<QV4::CallContext> callContext(scope, frame->callContext());
++ if (callContext) {
++ QV4::Heap::InternalClass *ic = callContext->internalClass();
++ QV4::ScopedValue v(scope);
++ for (uint i = 0; i < ic->size; ++i) {
++ QV4::ScopedValue stringOrSymbol(scope, ic->keyAt(i));
++ QV4::ScopedString propName(scope, stringOrSymbol->toString(scope.engine));
++ v = callContext->getProperty(propName);
++ collector.collect(&output, QString(), propName->toQString(), v);
++ }
++ }
++
++ response->insert(QStringLiteral("variables"), output);
++ }
++
++ void NativeDebugger::handleExpressions(QJsonObject *response, const QJsonObject &arguments)
++ {
++ TRACE_PROTOCOL("Evaluate expressions");
++ QV4::CppStackFrame *frame = nullptr;
++ decodeFrame(arguments.value(QLatin1String("context")).toString(), &frame);
++ if (!frame) {
++ setError(response, QStringLiteral("No stack frame passed"));
++ return;
++ }
++ TRACE_PROTOCOL("Context: " << executionContext);
++
++ QV4::ExecutionEngine *engine = frame->v4Function->internalClass->engine;
++ if (!engine) {
++ setError(response, QStringLiteral("No execution engine passed"));
++ return;
++ }
++ TRACE_PROTOCOL("Engines: " << engine << m_engine);
++
++ Collector collector(engine);
++ const QJsonArray expanded = arguments.value(QLatin1String("expanded")).toArray();
++ for (const QJsonValue ex : expanded)
++ collector.m_expanded.append(ex.toString());
++ TRACE_PROTOCOL("Expanded: " << collector.m_expanded);
++
++ QJsonArray output;
++ QV4::Scope scope(engine);
++
++ const QJsonArray expressions = arguments.value(QLatin1String("expressions")).toArray();
++ for (const QJsonValue expr : expressions) {
++ QString expression = expr.toObject().value(QLatin1String("expression")).toString();
++ QString name = expr.toObject().value(QLatin1String("name")).toString();
++ TRACE_PROTOCOL("Evaluate expression: " << expression);
++ m_runningJob = true;
++
++ QV4::ScopedValue result(scope, evaluateExpression(expression));
++
++ m_runningJob = false;
++ if (result->isUndefined()) {
++ QJsonObject dict;
++ dict.insert(QStringLiteral("name"), name);
++ dict.insert(QStringLiteral("valueencoded"), QStringLiteral("undefined"));
++ output.append(dict);
++ } else if (result.ptr && result.ptr->rawValue()) {
++ collector.collect(&output, QString(), name, *result);
++ } else {
++ QJsonObject dict;
++ dict.insert(QStringLiteral("name"), name);
++ dict.insert(QStringLiteral("valueencoded"), QStringLiteral("notaccessible"));
++ output.append(dict);
++ }
++ TRACE_PROTOCOL("EXCEPTION: " << engine->hasException);
++ engine->hasException = false;
++ }
++
++ response->insert(QStringLiteral("expressions"), output);
++ }
++
++ void BreakPointHandler::removeBreakPoint(int id)
++ {
++ for (int i = 0; i != m_breakPoints.size(); ++i) {
++ if (m_breakPoints.at(i).id == id) {
++ m_breakPoints.remove(i);
++ m_haveBreakPoints = !m_breakPoints.isEmpty();
++ return;
++ }
++ }
++ }
++
++ void BreakPointHandler::enableBreakPoint(int id, bool enabled)
++ {
++ m_breakPoints[id].enabled = enabled;
++ }
++
++ void NativeDebugger::pause()
++ {
++ m_pauseRequested = true;
++ }
++
++ void NativeDebugger::handleContinue(QJsonObject *response, Speed speed)
++ {
++ Q_UNUSED(response);
++
++ if (!m_returnedValue.isUndefined())
++ m_returnedValue.set(m_engine, QV4::Encode::undefined());
++
++ m_currentFrame = m_engine->currentStackFrame;
++ m_stepping = speed;
++ }
++
++ void NativeDebugger::maybeBreakAtInstruction()
++ {
++ if (m_runningJob) // do not re-enter when we're doing a job for the debugger.
++ return;
++
++ if (m_stepping == StepOver) {
++ if (m_currentFrame == m_engine->currentStackFrame)
++ pauseAndWait();
++ return;
++ }
++
++ if (m_stepping == StepIn) {
++ pauseAndWait();
++ return;
++ }
++
++ if (m_pauseRequested) { // Serve debugging requests from the agent
++ m_pauseRequested = false;
++ pauseAndWait();
++ return;
++ }
++
++ if (m_service->m_breakHandler->m_haveBreakPoints) {
++ if (QV4::Function *function = getFunction()) {
++ // lineNumber will be negative for Ret instructions, so those won't match
++ const int lineNumber = m_engine->currentStackFrame->lineNumber();
++ if (reallyHitTheBreakPoint(function, lineNumber))
++ pauseAndWait();
++ }
++ }
++ }
++
++ void NativeDebugger::enteringFunction()
++ {
++ if (m_runningJob)
++ return;
++
++ if (m_stepping == StepIn) {
++ m_currentFrame = m_engine->currentStackFrame;
++ }
++ }
++
++ void NativeDebugger::leavingFunction(const QV4::ReturnedValue &retVal)
++ {
++ if (m_runningJob)
++ return;
++
++ if (m_stepping != NotStepping && m_currentFrame == m_engine->currentStackFrame) {
++ m_currentFrame = m_currentFrame->parentFrame();
++ m_stepping = StepOver;
++ m_returnedValue.set(m_engine, retVal);
++ }
++ }
++
++ void NativeDebugger::aboutToThrow()
++ {
++ if (!m_service->m_breakHandler->m_breakOnThrow)
++ return;
++
++ if (m_runningJob) // do not re-enter when we're doing a job for the debugger.
++ return;
++
++ QJsonObject event;
++ // TODO: complete this!
++ event.insert(QStringLiteral("event"), QStringLiteral("exception"));
++ m_service->emitAsynchronousMessageToClient(event);
++ }
++
++ QV4::Function *NativeDebugger::getFunction() const
++ {
++ if (m_engine->currentStackFrame)
++ return m_engine->currentStackFrame->v4Function;
++ else
++ return m_engine->globalCode;
++ }
++
++ void NativeDebugger::pauseAndWait()
++ {
++ QJsonObject event;
++
++ event.insert(QStringLiteral("event"), QStringLiteral("break"));
++ event.insert(QStringLiteral("language"), QStringLiteral("js"));
++ if (QV4::CppStackFrame *frame = m_engine->currentStackFrame) {
++ QV4::Function *function = frame->v4Function;
++ event.insert(QStringLiteral("file"), function->sourceFile());
++ int line = frame->lineNumber();
++ event.insert(QStringLiteral("line"), (line < 0 ? -line : line));
++ }
++
++ m_service->emitAsynchronousMessageToClient(event);
++ }
++
++ bool NativeDebugger::reallyHitTheBreakPoint(const QV4::Function *function, int lineNumber)
++ {
++ for (int i = 0, n = m_service->m_breakHandler->m_breakPoints.size(); i != n; ++i) {
++ const BreakPoint &bp = m_service->m_breakHandler->m_breakPoints.at(i);
++ if (bp.lineNumber == lineNumber) {
++ const QString base = QUrl(function->sourceFile()).fileName();
++ if (bp.fileName.endsWith(base)) {
++ if (bp.condition.isEmpty() || checkCondition(bp.condition)) {
++ BreakPoint &mbp = m_service->m_breakHandler->m_breakPoints[i];
++ ++mbp.hitCount;
++ if (mbp.hitCount > mbp.ignoreCount)
++ return true;
++ }
++ }
++ }
++ }
++ return false;
++ }
++
++ QQmlNativeDebugServiceImpl::QQmlNativeDebugServiceImpl(QObject *parent)
++ : QQmlNativeDebugService(1.0, parent)
++ {
++ m_breakHandler = new BreakPointHandler;
++ }
++
++ QQmlNativeDebugServiceImpl::~QQmlNativeDebugServiceImpl()
++ {
++ delete m_breakHandler;
++ }
++
++ void QQmlNativeDebugServiceImpl::engineAboutToBeAdded(QJSEngine *engine)
++ {
++ TRACE_PROTOCOL("Adding engine" << engine);
++ if (engine) {
++ QV4::ExecutionEngine *ee = engine->handle();
++ TRACE_PROTOCOL("Adding execution engine" << ee);
++ if (ee) {
++ NativeDebugger *debugger = new NativeDebugger(this, ee);
++ if (state() == Enabled)
++ ee->setDebugger(debugger);
++ m_debuggers.append(QPointer<NativeDebugger>(debugger));
++ }
++ }
++ QQmlDebugService::engineAboutToBeAdded(engine);
++ }
++
++ void QQmlNativeDebugServiceImpl::engineAboutToBeRemoved(QJSEngine *engine)
++ {
++ TRACE_PROTOCOL("Removing engine" << engine);
++ if (engine) {
++ QV4::ExecutionEngine *executionEngine = engine->handle();
++ const auto debuggersCopy = m_debuggers;
++ for (NativeDebugger *debugger : debuggersCopy) {
++ if (debugger->engine() == executionEngine) {
++ m_debuggers.removeAll(debugger);
++ if (executionEngine->debugger() != debugger)
++ delete debugger;
++ }
++ }
++ }
++ QQmlDebugService::engineAboutToBeRemoved(engine);
++ }
++
++ void QQmlNativeDebugServiceImpl::stateAboutToBeChanged(QQmlDebugService::State state)
++ {
++ if (state == Enabled) {
++ for (NativeDebugger *debugger : std::as_const(m_debuggers)) {
++ QV4::ExecutionEngine *engine = debugger->engine();
++ if (!engine->debugger())
++ engine->setDebugger(debugger);
++ }
++ }
++ QQmlDebugService::stateAboutToBeChanged(state);
++ }
++
++ void QQmlNativeDebugServiceImpl::messageReceived(const QByteArray &message)
++ {
++ TRACE_PROTOCOL("Native message received: " << message);
++ QJsonObject request = QJsonDocument::fromJson(message).object();
++ QJsonObject response;
++ QJsonObject arguments = request.value(QLatin1String("arguments")).toObject();
++ QString cmd = request.value(QLatin1String("command")).toString();
++
++ if (cmd == QLatin1String("setbreakpoint")) {
++ m_breakHandler->handleSetBreakpoint(&response, arguments);
++ } else if (cmd == QLatin1String("removebreakpoint")) {
++ m_breakHandler->handleRemoveBreakpoint(&response, arguments);
++ } else if (cmd == QLatin1String("echo")) {
++ response.insert(QStringLiteral("result"), arguments);
++ } else {
++ for (NativeDebugger *debugger : std::as_const(m_debuggers))
++ if (debugger)
++ debugger->handleCommand(&response, cmd, arguments);
++ }
++ QJsonDocument doc;
++ doc.setObject(response);
++ QByteArray ba = doc.toJson(QJsonDocument::Compact);
++ TRACE_PROTOCOL("Sending synchronous response:" << ba.constData() << endl);
++ emit messageToClient(s_key, ba);
++ }
++
++ void QQmlNativeDebugServiceImpl::emitAsynchronousMessageToClient(const QJsonObject &message)
++ {
++ QJsonDocument doc;
++ doc.setObject(message);
++ QByteArray ba = doc.toJson(QJsonDocument::Compact);
++ TRACE_PROTOCOL("Sending asynchronous message:" << ba.constData() << endl);
++ emit messageToClient(s_key, ba);
++ }
++
++ QT_END_NAMESPACE