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 "qqmlnativedebugconnector.h"
++
++ #include <private/qhooks_p.h>
++ #include <private/qversionedpacket_p.h>
++
++ #include <QtQml/qjsengine.h>
++ #include <QtCore/qdebug.h>
++ #include <QtCore/qjsonarray.h>
++ #include <QtCore/qjsondocument.h>
++ #include <QtCore/qjsonobject.h>
++ #include <QtCore/qjsonvalue.h>
++ #include <QtCore/qpointer.h>
++ #include <QtCore/qvector.h>
++
++ //#define TRACE_PROTOCOL(s) qDebug() << s
++ #define TRACE_PROTOCOL(s)
++
++ QT_USE_NAMESPACE
++
++ static bool expectSyncronousResponse = false;
++ Q_GLOBAL_STATIC(QByteArray, responseBuffer)
++
++ #ifdef QT_SHARED
++ # define QML_DEBUG_EXPORT Q_DECL_EXPORT
++ #else
++ # define QML_DEBUG_EXPORT
++ #endif
++
++ extern "C" {
++
++ QML_DEBUG_EXPORT const char *qt_qmlDebugMessageBuffer;
++ QML_DEBUG_EXPORT int qt_qmlDebugMessageLength;
++ QML_DEBUG_EXPORT bool qt_qmlDebugConnectionBlocker;
++
++ // In blocking mode, this will busy wait until the debugger sets block to false.
++ QML_DEBUG_EXPORT void qt_qmlDebugConnectorOpen();
++
++ // First thing, set the debug stream version. Please use this function as we might move the version
++ // member to some other place.
++ QML_DEBUG_EXPORT void qt_qmlDebugSetStreamVersion(int version)
++ {
++ QQmlNativeDebugConnector::setDataStreamVersion(version);
++ }
++
++
++ // Break in this one to process output from an asynchronous message/
++ QML_DEBUG_EXPORT void qt_qmlDebugMessageAvailable()
++ {
++ }
++
++
++ // Break in this one to get notified about construction and destruction of
++ // interesting objects, such as QmlEngines.
++ QML_DEBUG_EXPORT void qt_qmlDebugObjectAvailable()
++ {
++ }
++
++ QML_DEBUG_EXPORT void qt_qmlDebugClearBuffer()
++ {
++ responseBuffer->clear();
++ qt_qmlDebugMessageBuffer = nullptr;
++ qt_qmlDebugMessageLength = 0;
++ }
++
++ // Send a message to a service.
++ QML_DEBUG_EXPORT bool qt_qmlDebugSendDataToService(const char *serviceName, const char *hexData)
++ {
++ QByteArray msg = QByteArray::fromHex(hexData);
++
++ QQmlDebugConnector *instance = QQmlDebugConnector::instance();
++ if (!instance)
++ return false;
++
++ QQmlDebugService *recipient = instance->service(serviceName);
++ if (!recipient)
++ return false;
++
++ TRACE_PROTOCOL("Recipient: " << recipient << " got message: " << msg);
++ expectSyncronousResponse = true;
++ recipient->messageReceived(msg);
++ expectSyncronousResponse = false;
++
++ return true;
++ }
++
++ // Enable a service.
++ QML_DEBUG_EXPORT bool qt_qmlDebugEnableService(const char *data)
++ {
++ QQmlDebugConnector *instance = QQmlDebugConnector::instance();
++ if (!instance)
++ return false;
++
++ QString name = QString::fromLatin1(data);
++ QQmlDebugService *service = instance->service(name);
++ if (!service || service->state() == QQmlDebugService::Enabled)
++ return false;
++
++ service->stateAboutToBeChanged(QQmlDebugService::Enabled);
++ service->setState(QQmlDebugService::Enabled);
++ service->stateChanged(QQmlDebugService::Enabled);
++ return true;
++ }
++
++ QML_DEBUG_EXPORT bool qt_qmlDebugDisableService(const char *data)
++ {
++ QQmlDebugConnector *instance = QQmlDebugConnector::instance();
++ if (!instance)
++ return false;
++
++ QString name = QString::fromLatin1(data);
++ QQmlDebugService *service = instance->service(name);
++ if (!service || service->state() == QQmlDebugService::Unavailable)
++ return false;
++
++ service->stateAboutToBeChanged(QQmlDebugService::Unavailable);
++ service->setState(QQmlDebugService::Unavailable);
++ service->stateChanged(QQmlDebugService::Unavailable);
++ return true;
++ }
++
++ quintptr qt_qmlDebugTestHooks[] = {
++ quintptr(1), // Internal Version
++ quintptr(6), // Number of entries following
++ quintptr(&qt_qmlDebugMessageBuffer),
++ quintptr(&qt_qmlDebugMessageLength),
++ quintptr(&qt_qmlDebugSendDataToService),
++ quintptr(&qt_qmlDebugEnableService),
++ quintptr(&qt_qmlDebugDisableService),
++ quintptr(&qt_qmlDebugObjectAvailable),
++ quintptr(&qt_qmlDebugClearBuffer)
++ };
++
++ // In blocking mode, this will busy wait until the debugger sets block to false.
++ QML_DEBUG_EXPORT void qt_qmlDebugConnectorOpen()
++ {
++ TRACE_PROTOCOL("Opening native debug connector");
++
++ // FIXME: Use a dedicated hook. Startup is a safe workaround, though,
++ // as we are already beyond its only use.
++ qtHookData[QHooks::Startup] = quintptr(&qt_qmlDebugTestHooks);
++
++ while (qt_qmlDebugConnectionBlocker)
++ ;
++
++ TRACE_PROTOCOL("Opened native debug connector");
++ }
++
++ } // extern "C"
++
++ QT_BEGIN_NAMESPACE
++
++ QQmlNativeDebugConnector::QQmlNativeDebugConnector()
++ : m_blockingMode(false)
++ {
++ const QString args = commandLineArguments();
++ const auto lstjsDebugArguments = QStringView{args}.split(QLatin1Char(','), Qt::SkipEmptyParts);
++ QStringList services;
++ for (const QStringView &strArgument : lstjsDebugArguments) {
++ if (strArgument == QLatin1String("block")) {
++ m_blockingMode = true;
++ } else if (strArgument == QLatin1String("native")) {
++ // Ignore. This is used to signal that this connector
++ // should be loaded and that has already happened.
++ } else if (strArgument.startsWith(QLatin1String("services:"))) {
++ services.append(strArgument.mid(9).toString());
++ } else if (!services.isEmpty()) {
++ services.append(strArgument.toString());
++ } else if (!strArgument.startsWith(QLatin1String("connector:"))) {
++ qWarning("QML Debugger: Invalid argument \"%s\" detected. Ignoring the same.",
++ strArgument.toUtf8().constData());
++ }
++ }
++ setServices(services);
++ }
++
++ QQmlNativeDebugConnector::~QQmlNativeDebugConnector()
++ {
++ for (QQmlDebugService *service : std::as_const(m_services)) {
++ service->stateAboutToBeChanged(QQmlDebugService::NotConnected);
++ service->setState(QQmlDebugService::NotConnected);
++ service->stateChanged(QQmlDebugService::NotConnected);
++ }
++ }
++
++ bool QQmlNativeDebugConnector::blockingMode() const
++ {
++ return m_blockingMode;
++ }
++
++ QQmlDebugService *QQmlNativeDebugConnector::service(const QString &name) const
++ {
++ for (QList<QQmlDebugService *>::ConstIterator i = m_services.begin(); i != m_services.end();
++ ++i) {
++ if ((*i)->name() == name)
++ return *i;
++ }
++ return nullptr;
++ }
++
++ void QQmlNativeDebugConnector::addEngine(QJSEngine *engine)
++ {
++ Q_ASSERT(!m_engines.contains(engine));
++
++ TRACE_PROTOCOL("Add engine to connector:" << engine);
++ for (QQmlDebugService *service : std::as_const(m_services))
++ service->engineAboutToBeAdded(engine);
++
++ announceObjectAvailability(QLatin1String("qmlengine"), engine, true);
++
++ for (QQmlDebugService *service : std::as_const(m_services))
++ service->engineAdded(engine);
++
++ m_engines.append(engine);
++ }
++
++ void QQmlNativeDebugConnector::removeEngine(QJSEngine *engine)
++ {
++ Q_ASSERT(m_engines.contains(engine));
++
++ TRACE_PROTOCOL("Remove engine from connector:" << engine);
++ for (QQmlDebugService *service : std::as_const(m_services))
++ service->engineAboutToBeRemoved(engine);
++
++ announceObjectAvailability(QLatin1String("qmlengine"), engine, false);
++
++ for (QQmlDebugService *service : std::as_const(m_services))
++ service->engineRemoved(engine);
++
++ m_engines.removeOne(engine);
++ }
++
++ bool QQmlNativeDebugConnector::hasEngine(QJSEngine *engine) const
++ {
++ return m_engines.contains(engine);
++ }
++
++ void QQmlNativeDebugConnector::announceObjectAvailability(const QString &objectType,
++ QObject *object, bool available)
++ {
++ QJsonObject ob;
++ ob.insert(QLatin1String("objecttype"), objectType);
++ ob.insert(QLatin1String("object"), QString::number(quintptr(object)));
++ ob.insert(QLatin1String("available"), available);
++ QJsonDocument doc;
++ doc.setObject(ob);
++
++ QByteArray ba = doc.toJson(QJsonDocument::Compact);
++ qt_qmlDebugMessageBuffer = ba.constData();
++ qt_qmlDebugMessageLength = ba.size();
++ TRACE_PROTOCOL("Reporting engine availabilty");
++ qt_qmlDebugObjectAvailable(); // Trigger native breakpoint.
++ qt_qmlDebugMessageBuffer = nullptr;
++ qt_qmlDebugMessageLength = 0;
++ }
++
++ bool QQmlNativeDebugConnector::addService(const QString &name, QQmlDebugService *service)
++ {
++ TRACE_PROTOCOL("Add service to connector: " << qPrintable(name) << service);
++ for (auto it = m_services.cbegin(), end = m_services.cend(); it != end; ++it) {
++ if ((*it)->name() == name)
++ return false;
++ }
++
++ connect(service, &QQmlDebugService::messageToClient,
++ this, &QQmlNativeDebugConnector::sendMessage);
++ connect(service, &QQmlDebugService::messagesToClient,
++ this, &QQmlNativeDebugConnector::sendMessages);
++
++ service->setState(QQmlDebugService::Unavailable);
++
++ m_services << service;
++ return true;
++ }
++
++ bool QQmlNativeDebugConnector::removeService(const QString &name)
++ {
++ for (QList<QQmlDebugService *>::Iterator i = m_services.begin(); i != m_services.end(); ++i) {
++ if ((*i)->name() == name) {
++ QQmlDebugService *service = *i;
++ m_services.erase(i);
++ service->setState(QQmlDebugService::NotConnected);
++
++ disconnect(service, &QQmlDebugService::messagesToClient,
++ this, &QQmlNativeDebugConnector::sendMessages);
++ disconnect(service, &QQmlDebugService::messageToClient,
++ this, &QQmlNativeDebugConnector::sendMessage);
++
++ return true;
++ }
++ }
++ return false;
++ }
++
++ bool QQmlNativeDebugConnector::open(const QVariantHash &configuration)
++ {
++ m_blockingMode = configuration.value(QStringLiteral("block"), m_blockingMode).toBool();
++ qt_qmlDebugConnectionBlocker = m_blockingMode;
++ qt_qmlDebugConnectorOpen();
++ return true;
++ }
++
++ void QQmlNativeDebugConnector::setDataStreamVersion(int version)
++ {
++ Q_ASSERT(version <= QDataStream::Qt_DefaultCompiledVersion);
++ s_dataStreamVersion = version;
++ }
++
++ void QQmlNativeDebugConnector::sendMessage(const QString &name, const QByteArray &message)
++ {
++ (*responseBuffer) += name.toUtf8() + ' ' + QByteArray::number(message.size()) + ' ' + message;
++ qt_qmlDebugMessageBuffer = responseBuffer->constData();
++ qt_qmlDebugMessageLength = responseBuffer->size();
++ // Responses are allowed to accumulate, the buffer will be cleared by
++ // separate calls to qt_qmlDebugClearBuffer() once the synchronous
++ // function return ('if' branch below) or in the native breakpoint handler
++ // ('else' branch below).
++ if (expectSyncronousResponse) {
++ TRACE_PROTOCOL("Expected synchronous response in " << message);
++ // Do not trigger the native breakpoint on qt_qmlDebugMessageFromService.
++ } else {
++ TRACE_PROTOCOL("Found asynchronous message in " << message);
++ // Trigger native breakpoint.
++ qt_qmlDebugMessageAvailable();
++ }
++ }
++
++ void QQmlNativeDebugConnector::sendMessages(const QString &name, const QList<QByteArray> &messages)
++ {
++ for (int i = 0; i != messages.size(); ++i)
++ sendMessage(name, messages.at(i));
++ }
++
++ QQmlDebugConnector *QQmlNativeDebugConnectorFactory::create(const QString &key)
++ {
++ return key == QLatin1String("QQmlNativeDebugConnector") ? new QQmlNativeDebugConnector : nullptr;
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qqmlnativedebugconnector.cpp"