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

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