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

source code of qtdeclarative/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.cpp