Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlnativedebugconnector.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant
4
6
7#include <private/qhooks_p.h>
8#include <private/qversionedpacket_p.h>
9
10#include <QtQml/qjsengine.h>
11#include <QtCore/qdebug.h>
12#include <QtCore/qjsonarray.h>
13#include <QtCore/qjsondocument.h>
14#include <QtCore/qjsonobject.h>
15#include <QtCore/qjsonvalue.h>
16#include <QtCore/qpointer.h>
17#include <QtCore/qvector.h>
18
19//#define TRACE_PROTOCOL(s) qDebug() << s
20#define TRACE_PROTOCOL(s)
21
22QT_USE_NAMESPACE
23
24static bool expectSyncronousResponse = false;
26
27#ifdef QT_SHARED
28# define QML_DEBUG_EXPORT Q_DECL_EXPORT
29#else
30# define QML_DEBUG_EXPORT
31#endif
32
33extern "C" {
34
38
39// In blocking mode, this will busy wait until the debugger sets block to false.
41
42// First thing, set the debug stream version. Please use this function as we might move the version
43// member to some other place.
45{
46 QQmlNativeDebugConnector::setDataStreamVersion(version);
47}
48
49
50// Break in this one to process output from an asynchronous message/
54
55
56// Break in this one to get notified about construction and destruction of
57// interesting objects, such as QmlEngines.
61
63{
64 responseBuffer->clear();
67}
68
69// Send a message to a service.
70QML_DEBUG_EXPORT bool qt_qmlDebugSendDataToService(const char *serviceName, const char *hexData)
71{
72 QByteArray msg = QByteArray::fromHex(hexData);
73
74 QQmlDebugConnector *instance = QQmlDebugConnector::instance();
75 if (!instance)
76 return false;
77
78 QQmlDebugService *recipient = instance->service(serviceName);
79 if (!recipient)
80 return false;
81
82 TRACE_PROTOCOL("Recipient: " << recipient << " got message: " << msg);
84 recipient->messageReceived(msg);
86
87 return true;
88}
89
90// Enable a service.
92{
93 QQmlDebugConnector *instance = QQmlDebugConnector::instance();
94 if (!instance)
95 return false;
96
97 QString name = QString::fromLatin1(data);
98 QQmlDebugService *service = instance->service(name);
99 if (!service || service->state() == QQmlDebugService::Enabled)
100 return false;
101
102 service->stateAboutToBeChanged(QQmlDebugService::Enabled);
103 service->setState(QQmlDebugService::Enabled);
104 service->stateChanged(QQmlDebugService::Enabled);
105 return true;
106}
107
109{
110 QQmlDebugConnector *instance = QQmlDebugConnector::instance();
111 if (!instance)
112 return false;
113
114 QString name = QString::fromLatin1(data);
115 QQmlDebugService *service = instance->service(name);
116 if (!service || service->state() == QQmlDebugService::Unavailable)
117 return false;
118
119 service->stateAboutToBeChanged(QQmlDebugService::Unavailable);
120 service->setState(QQmlDebugService::Unavailable);
121 service->stateChanged(QQmlDebugService::Unavailable);
122 return true;
123}
124
126 quintptr(1), // Internal Version
127 quintptr(6), // Number of entries following
128 quintptr(&qt_qmlDebugMessageBuffer),
129 quintptr(&qt_qmlDebugMessageLength),
131 quintptr(&qt_qmlDebugEnableService),
132 quintptr(&qt_qmlDebugDisableService),
134 quintptr(&qt_qmlDebugClearBuffer)
135};
136
137// In blocking mode, this will busy wait until the debugger sets block to false.
139{
140 TRACE_PROTOCOL("Opening native debug connector");
141
142 // FIXME: Use a dedicated hook. Startup is a safe workaround, though,
143 // as we are already beyond its only use.
144 qtHookData[QHooks::Startup] = quintptr(&qt_qmlDebugTestHooks);
145
147 ;
148
149 TRACE_PROTOCOL("Opened native debug connector");
150}
151
152} // extern "C"
153
154QT_BEGIN_NAMESPACE
155
156QQmlNativeDebugConnector::QQmlNativeDebugConnector()
157 : m_blockingMode(false)
158{
159 const QString args = commandLineArguments();
160 const auto lstjsDebugArguments = QStringView{args}.split(QLatin1Char(','), Qt::SkipEmptyParts);
161 QStringList services;
162 for (const QStringView &strArgument : lstjsDebugArguments) {
163 if (strArgument == QLatin1String("block")) {
164 m_blockingMode = true;
165 } else if (strArgument == QLatin1String("native")) {
166 // Ignore. This is used to signal that this connector
167 // should be loaded and that has already happened.
168 } else if (strArgument.startsWith(QLatin1String("services:"))) {
169 services.append(strArgument.mid(9).toString());
170 } else if (!services.isEmpty()) {
171 services.append(strArgument.toString());
172 } else if (!strArgument.startsWith(QLatin1String("connector:"))) {
173 qWarning("QML Debugger: Invalid argument \"%s\" detected. Ignoring the same.",
174 strArgument.toUtf8().constData());
175 }
176 }
177 setServices(services);
178}
179
180QQmlNativeDebugConnector::~QQmlNativeDebugConnector()
181{
182 for (QQmlDebugService *service : std::as_const(m_services)) {
183 service->stateAboutToBeChanged(QQmlDebugService::NotConnected);
184 service->setState(QQmlDebugService::NotConnected);
185 service->stateChanged(QQmlDebugService::NotConnected);
186 }
187}
188
189bool QQmlNativeDebugConnector::blockingMode() const
190{
191 return m_blockingMode;
192}
193
194QQmlDebugService *QQmlNativeDebugConnector::service(const QString &name) const
195{
196 for (QVector<QQmlDebugService *>::ConstIterator i = m_services.begin(); i != m_services.end();
197 ++i) {
198 if ((*i)->name() == name)
199 return *i;
200 }
201 return nullptr;
202}
203
204void QQmlNativeDebugConnector::addEngine(QJSEngine *engine)
205{
206 Q_ASSERT(!m_engines.contains(engine));
207
208 TRACE_PROTOCOL("Add engine to connector:" << engine);
209 for (QQmlDebugService *service : std::as_const(m_services))
210 service->engineAboutToBeAdded(engine);
211
212 announceObjectAvailability(QLatin1String("qmlengine"), engine, true);
213
214 for (QQmlDebugService *service : std::as_const(m_services))
215 service->engineAdded(engine);
216
217 m_engines.append(engine);
218}
219
220void QQmlNativeDebugConnector::removeEngine(QJSEngine *engine)
221{
222 Q_ASSERT(m_engines.contains(engine));
223
224 TRACE_PROTOCOL("Remove engine from connector:" << engine);
225 for (QQmlDebugService *service : std::as_const(m_services))
226 service->engineAboutToBeRemoved(engine);
227
228 announceObjectAvailability(QLatin1String("qmlengine"), engine, false);
229
230 for (QQmlDebugService *service : std::as_const(m_services))
231 service->engineRemoved(engine);
232
233 m_engines.removeOne(engine);
234}
235
236bool QQmlNativeDebugConnector::hasEngine(QJSEngine *engine) const
237{
238 return m_engines.contains(engine);
239}
240
241void QQmlNativeDebugConnector::announceObjectAvailability(const QString &objectType,
242 QObject *object, bool available)
243{
244 QJsonObject ob;
245 ob.insert(QLatin1String("objecttype"), objectType);
246 ob.insert(QLatin1String("object"), QString::number(quintptr(object)));
247 ob.insert(QLatin1String("available"), available);
248 QJsonDocument doc;
249 doc.setObject(ob);
250
251 QByteArray ba = doc.toJson(QJsonDocument::Compact);
252 qt_qmlDebugMessageBuffer = ba.constData();
253 qt_qmlDebugMessageLength = ba.size();
254 TRACE_PROTOCOL("Reporting engine availabilty");
255 qt_qmlDebugObjectAvailable(); // Trigger native breakpoint.
256 qt_qmlDebugMessageBuffer = nullptr;
258}
259
260bool QQmlNativeDebugConnector::addService(const QString &name, QQmlDebugService *service)
261{
262 TRACE_PROTOCOL("Add service to connector: " << qPrintable(name) << service);
263 for (auto it = m_services.cbegin(), end = m_services.cend(); it != end; ++it) {
264 if ((*it)->name() == name)
265 return false;
266 }
267
268 connect(service, &QQmlDebugService::messageToClient,
269 this, &QQmlNativeDebugConnector::sendMessage);
270 connect(service, &QQmlDebugService::messagesToClient,
271 this, &QQmlNativeDebugConnector::sendMessages);
272
273 service->setState(QQmlDebugService::Unavailable);
274
275 m_services << service;
276 return true;
277}
278
279bool QQmlNativeDebugConnector::removeService(const QString &name)
280{
281 for (QVector<QQmlDebugService *>::Iterator i = m_services.begin(); i != m_services.end(); ++i) {
282 if ((*i)->name() == name) {
283 QQmlDebugService *service = *i;
284 m_services.erase(i);
285 service->setState(QQmlDebugService::NotConnected);
286
287 disconnect(service, &QQmlDebugService::messagesToClient,
288 this, &QQmlNativeDebugConnector::sendMessages);
289 disconnect(service, &QQmlDebugService::messageToClient,
290 this, &QQmlNativeDebugConnector::sendMessage);
291
292 return true;
293 }
294 }
295 return false;
296}
297
298bool QQmlNativeDebugConnector::open(const QVariantHash &configuration)
299{
300 m_blockingMode = configuration.value(QStringLiteral("block"), m_blockingMode).toBool();
301 qt_qmlDebugConnectionBlocker = m_blockingMode;
303 return true;
304}
305
306void QQmlNativeDebugConnector::setDataStreamVersion(int version)
307{
308 Q_ASSERT(version <= QDataStream::Qt_DefaultCompiledVersion);
309 s_dataStreamVersion = version;
310}
311
312void QQmlNativeDebugConnector::sendMessage(const QString &name, const QByteArray &message)
313{
314 (*responseBuffer) += name.toUtf8() + ' ' + QByteArray::number(message.size()) + ' ' + message;
315 qt_qmlDebugMessageBuffer = responseBuffer->constData();
316 qt_qmlDebugMessageLength = responseBuffer->size();
317 // Responses are allowed to accumulate, the buffer will be cleared by
318 // separate calls to qt_qmlDebugClearBuffer() once the synchronous
319 // function return ('if' branch below) or in the native breakpoint handler
320 // ('else' branch below).
322 TRACE_PROTOCOL("Expected synchronous response in " << message);
323 // Do not trigger the native breakpoint on qt_qmlDebugMessageFromService.
324 } else {
325 TRACE_PROTOCOL("Found asynchronous message in " << message);
326 // Trigger native breakpoint.
328 }
329}
330
331void QQmlNativeDebugConnector::sendMessages(const QString &name, const QList<QByteArray> &messages)
332{
333 for (int i = 0; i != messages.size(); ++i)
334 sendMessage(name, messages.at(i));
335}
336
337QQmlDebugConnector *QQmlNativeDebugConnectorFactory::create(const QString &key)
338{
339 return key == QLatin1String("QQmlNativeDebugConnector") ? new QQmlNativeDebugConnector : nullptr;
340}
341
342QT_END_NAMESPACE
343
344#include "moc_qqmlnativedebugconnector.cpp"
Q_GLOBAL_STATIC(QReadWriteLock, g_updateMutex)
quintptr qt_qmlDebugTestHooks[]
QML_DEBUG_EXPORT const char * qt_qmlDebugMessageBuffer
QML_DEBUG_EXPORT void qt_qmlDebugClearBuffer()
QML_DEBUG_EXPORT void qt_qmlDebugSetStreamVersion(int version)
QML_DEBUG_EXPORT int qt_qmlDebugMessageLength
QML_DEBUG_EXPORT void qt_qmlDebugConnectorOpen()
QML_DEBUG_EXPORT bool qt_qmlDebugSendDataToService(const char *serviceName, const char *hexData)
QML_DEBUG_EXPORT void qt_qmlDebugObjectAvailable()
QML_DEBUG_EXPORT void qt_qmlDebugMessageAvailable()
QML_DEBUG_EXPORT bool qt_qmlDebugDisableService(const char *data)
static QT_USE_NAMESPACE bool expectSyncronousResponse
#define QML_DEBUG_EXPORT
QML_DEBUG_EXPORT bool qt_qmlDebugEnableService(const char *data)
QML_DEBUG_EXPORT bool qt_qmlDebugConnectionBlocker
#define TRACE_PROTOCOL(x)