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

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