1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtDeclarative 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 http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://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 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "private/qjsdebugservice_p.h"
43 #include "private/qjsdebuggeragent_p.h"
44 
45 #include <QtCore/qdatastream.h>
46 #include <QtCore/qdebug.h>
47 #include <QtCore/qstringlist.h>
48 #include <QtDeclarative/qdeclarativeengine.h>
49 
Q_GLOBAL_STATIC(QJSDebugService,serviceInstance)50 Q_GLOBAL_STATIC(QJSDebugService, serviceInstance)
51 
52 // convert to a QByteArray that can be sent to the debug client
53 QByteArray JSAgentCoverageData::toByteArray() const
54 {
55     QByteArray data;
56     //### using QDataStream is relatively expensive
57     QDataStream ds(&data, QIODevice::WriteOnly);
58     ds << prefix << time << messageType << scriptId << program << fileName << baseLineNumber
59         << lineNumber << columnNumber << returnValue;
60     return data;
61 }
62 
QJSDebugService(QObject * parent)63 QJSDebugService::QJSDebugService(QObject *parent)
64     : QDeclarativeDebugService(QLatin1String("JSDebugger"), parent)
65     , m_agent(0), m_deferredSend(true)
66 {
67     m_timer.start();
68 }
69 
~QJSDebugService()70 QJSDebugService::~QJSDebugService()
71 {
72     delete m_agent;
73 }
74 
instance()75 QJSDebugService *QJSDebugService::instance()
76 {
77     return serviceInstance();
78 }
79 
addEngine(QDeclarativeEngine * engine)80 void QJSDebugService::addEngine(QDeclarativeEngine *engine)
81 {
82     Q_ASSERT(engine);
83     Q_ASSERT(!m_engines.contains(engine));
84 
85     m_engines.append(engine);
86 
87     if (status() == Enabled && !m_engines.isEmpty() && !m_agent) {
88         m_agent = new QJSDebuggerAgent(engine, engine);
89         connect(m_agent, SIGNAL(stopped(bool,QString)),
90                 this, SLOT(executionStopped(bool,QString)));
91 
92         while (!m_agent->isInitialized()) {
93             waitForMessage();
94         }
95     }
96 }
97 
removeEngine(QDeclarativeEngine * engine)98 void QJSDebugService::removeEngine(QDeclarativeEngine *engine)
99 {
100     Q_ASSERT(engine);
101     Q_ASSERT(m_engines.contains(engine));
102 
103     m_engines.removeAll(engine);
104 }
105 
statusChanged(Status status)106 void QJSDebugService::statusChanged(Status status)
107 {
108     if (status == Enabled && !m_engines.isEmpty() && !m_agent) {
109         // Multiple engines are currently unsupported
110         QDeclarativeEngine *engine = m_engines.first();
111         m_agent = new QJSDebuggerAgent(engine, engine);
112 
113         connect(m_agent, SIGNAL(stopped(bool,QString)),
114                 this, SLOT(executionStopped(bool,QString)));
115 
116     } else if (status != Enabled && m_agent) {
117         delete m_agent;
118         m_agent = 0;
119     }
120 }
121 
messageReceived(const QByteArray & message)122 void QJSDebugService::messageReceived(const QByteArray &message)
123 {
124     if (!m_agent) {
125         qWarning() << "QJSDebugService::messageReceived: No QJSDebuggerAgent available";
126         return;
127     }
128 
129     QDataStream ds(message);
130     QByteArray command;
131     ds >> command;
132     if (command == "BREAKPOINTS") {
133         JSAgentBreakpoints breakpoints;
134         ds >> breakpoints;
135         m_agent->setBreakpoints(breakpoints);
136 
137         //qDebug() << "BREAKPOINTS";
138         //foreach (const JSAgentBreakpointData &bp, breakpoints)
139         //    qDebug() << "BREAKPOINT: " << bp.fileUrl << bp.lineNumber;
140     } else if (command == "WATCH_EXPRESSIONS") {
141         QStringList watchExpressions;
142         ds >> watchExpressions;
143         m_agent->setWatchExpressions(watchExpressions);
144     } else if (command == "STEPOVER") {
145         m_agent->stepOver();
146     } else if (command == "STEPINTO" || command == "INTERRUPT") {
147         m_agent->stepInto();
148     } else if (command == "STEPOUT") {
149         m_agent->stepOut();
150     } else if (command == "CONTINUE") {
151         m_agent->continueExecution();
152     } else if (command == "EXEC") {
153         QByteArray id;
154         QString expr;
155         ds >> id >> expr;
156 
157         JSAgentWatchData data = m_agent->executeExpression(expr);
158 
159         QByteArray reply;
160         QDataStream rs(&reply, QIODevice::WriteOnly);
161         rs << QByteArray("RESULT") << id << data;
162         sendMessage(reply);
163     } else if (command == "EXPAND") {
164         QByteArray requestId;
165         quint64 objectId;
166         ds >> requestId >> objectId;
167 
168         QList<JSAgentWatchData> result = m_agent->expandObjectById(objectId);
169 
170         QByteArray reply;
171         QDataStream rs(&reply, QIODevice::WriteOnly);
172         rs << QByteArray("EXPANDED") << requestId << result;
173         sendMessage(reply);
174     } else if (command == "ACTIVATE_FRAME") {
175         int frameId;
176         ds >> frameId;
177 
178         QList<JSAgentWatchData> locals = m_agent->localsAtFrame(frameId);
179 
180         QByteArray reply;
181         QDataStream rs(&reply, QIODevice::WriteOnly);
182         rs << QByteArray("LOCALS") << frameId << locals;
183         sendMessage(reply);
184     } else if (command == "SET_PROPERTY") {
185         QByteArray id;
186         qint64 objectId;
187         QString property;
188         QString value;
189         ds >> id >> objectId >> property >> value;
190 
191         m_agent->setProperty(objectId, property, value);
192 
193         //TODO: feedback
194     } else if (command == "PING") {
195         int ping;
196         ds >> ping;
197         QByteArray reply;
198         QDataStream rs(&reply, QIODevice::WriteOnly);
199         rs << QByteArray("PONG") << ping;
200         sendMessage(reply);
201     } else if (command == "COVERAGE") {
202         bool enabled;
203         ds >> enabled;
204         m_agent->setCoverageEnabled(enabled);
205         if (!enabled) {
206             sendMessages();
207         }
208     } else {
209         qDebug() << Q_FUNC_INFO << "Unknown command" << command;
210     }
211 
212     QDeclarativeDebugService::messageReceived(message);
213 }
214 
executionStopped(bool becauseOfException,const QString & exception)215 void QJSDebugService::executionStopped(bool becauseOfException,
216                                        const QString &exception)
217 {
218     const QList<JSAgentStackData> backtrace = m_agent->backtrace();
219     const QList<JSAgentWatchData> watches = m_agent->watches();
220     const QList<JSAgentWatchData> locals = m_agent->locals();
221 
222     QByteArray reply;
223     QDataStream rs(&reply, QIODevice::WriteOnly);
224     rs << QByteArray("STOPPED") << backtrace << watches << locals
225        << becauseOfException << exception;
226     sendMessage(reply);
227 }
228 
229 /*
230     Either send the message directly, or queue up
231     a list of messages to send later (via sendMessages)
232 */
processMessage(const JSAgentCoverageData & message)233 void QJSDebugService::processMessage(const JSAgentCoverageData &message)
234 {
235     if (m_deferredSend)
236         m_data.append(message);
237     else
238         sendMessage(message.toByteArray());
239 }
240 
241 /*
242     Send the messages queued up by processMessage
243 */
sendMessages()244 void QJSDebugService::sendMessages()
245 {
246     if (m_deferredSend) {
247         //### this is a suboptimal way to send batched messages
248         for (int i = 0; i < m_data.count(); ++i)
249             sendMessage(m_data.at(i).toByteArray());
250         m_data.clear();
251 
252         //indicate completion
253         QByteArray data;
254         QDataStream ds(&data, QIODevice::WriteOnly);
255         ds << QByteArray("COVERAGE") << (qint64)-1 << (int)CoverageComplete;
256         sendMessage(data);
257     }
258 }
259 
260