1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at info@qt.nokia.com.
31 **************************************************************************/
32 #include "qscriptdebuggerclient.h"
34 #include "watchdata.h"
35 #include "watchhandler.h"
36 #include "breakpoint.h"
37 #include "breakhandler.h"
38 #include "debuggerconstants.h"
39 #include "qmlengine.h"
40 #include "stackhandler.h"
41 #include "debuggercore.h"
43 #include <QTextDocument>
45 #include <QMessageBox>
46 #include <extensionsystem/pluginmanager.h>
47 #include <utils/qtcassert.h>
52 struct JSAgentBreakpointData
54 QByteArray functionName;
59 struct JSAgentStackData
61 QByteArray functionName;
66 uint qHash(const JSAgentBreakpointData &b)
68 return b.lineNumber ^ qHash(b.fileUrl);
71 QDataStream &operator<<(QDataStream &s, const JSAgentBreakpointData &data)
73 return s << data.functionName << data.fileUrl << data.lineNumber;
76 QDataStream &operator<<(QDataStream &s, const JSAgentStackData &data)
78 return s << data.functionName << data.fileUrl << data.lineNumber;
81 QDataStream &operator>>(QDataStream &s, JSAgentBreakpointData &data)
83 return s >> data.functionName >> data.fileUrl >> data.lineNumber;
86 QDataStream &operator>>(QDataStream &s, JSAgentStackData &data)
88 return s >> data.functionName >> data.fileUrl >> data.lineNumber;
91 bool operator==(const JSAgentBreakpointData &b1, const JSAgentBreakpointData &b2)
93 return b1.lineNumber == b2.lineNumber && b1.fileUrl == b2.fileUrl;
96 typedef QSet<JSAgentBreakpointData> JSAgentBreakpoints;
97 typedef QList<JSAgentStackData> JSAgentStackFrames;
100 static QDataStream &operator>>(QDataStream &s, WatchData &data)
106 bool hasChildren = false;
107 s >> data.exp >> name >> value >> type >> hasChildren >> data.id;
108 data.name = QString::fromUtf8(name);
109 data.setType(type, false);
110 data.setValue(QString::fromUtf8(value));
111 data.setHasChildren(hasChildren);
112 data.setAllUnneeded();
116 class QScriptDebuggerClientPrivate
119 explicit QScriptDebuggerClientPrivate(QScriptDebuggerClient *) :
127 JSAgentBreakpoints breakpoints;
130 QScriptDebuggerClient::QScriptDebuggerClient(QmlJsDebugClient::QDeclarativeDebugConnection* client)
131 : QmlDebuggerClient(client, QLatin1String("JSDebugger")),
132 d(new QScriptDebuggerClientPrivate(this))
136 QScriptDebuggerClient::~QScriptDebuggerClient()
141 void QScriptDebuggerClient::executeStep()
144 QDataStream rs(&reply, QIODevice::WriteOnly);
145 QByteArray cmd = "STEPINTO";
150 void QScriptDebuggerClient::executeStepOut()
153 QDataStream rs(&reply, QIODevice::WriteOnly);
154 QByteArray cmd = "STEPOUT";
159 void QScriptDebuggerClient::executeNext()
162 QDataStream rs(&reply, QIODevice::WriteOnly);
163 QByteArray cmd = "STEPOVER";
168 void QScriptDebuggerClient::executeStepI()
171 QDataStream rs(&reply, QIODevice::WriteOnly);
172 QByteArray cmd = "STEPINTO";
177 void QScriptDebuggerClient::continueInferior()
180 QDataStream rs(&reply, QIODevice::WriteOnly);
181 QByteArray cmd = "CONTINUE";
186 void QScriptDebuggerClient::interruptInferior()
189 QDataStream rs(&reply, QIODevice::WriteOnly);
190 QByteArray cmd = "INTERRUPT";
195 void QScriptDebuggerClient::connect()
199 void QScriptDebuggerClient::disconnect()
203 void QScriptDebuggerClient::activateFrame(int index)
206 QDataStream rs(&reply, QIODevice::WriteOnly);
207 QByteArray cmd = "ACTIVATE_FRAME";
213 void QScriptDebuggerClient::insertBreakpoint(BreakpointModelId id)
215 BreakHandler *handler = d->engine->breakHandler();
216 JSAgentBreakpointData bp;
217 bp.fileUrl = QUrl::fromLocalFile(handler->fileName(id)).toString().toUtf8();
218 bp.lineNumber = handler->lineNumber(id);
219 bp.functionName = handler->functionName(id).toUtf8();
220 d->breakpoints.insert(bp);
223 void QScriptDebuggerClient::removeBreakpoint(BreakpointModelId id)
225 BreakHandler *handler = d->engine->breakHandler();
226 JSAgentBreakpointData bp;
227 bp.fileUrl = QUrl::fromLocalFile(handler->fileName(id)).toString().toUtf8();
228 bp.lineNumber = handler->lineNumber(id);
229 bp.functionName = handler->functionName(id).toUtf8();
230 d->breakpoints.remove(bp);
233 void QScriptDebuggerClient::changeBreakpoint(BreakpointModelId /*id*/)
237 void QScriptDebuggerClient::updateBreakpoints()
240 QDataStream rs(&reply, QIODevice::WriteOnly);
241 QByteArray cmd = "BREAKPOINTS";
247 void QScriptDebuggerClient::assignValueInDebugger(const QByteArray expr, const quint64 &id,
248 const QString &property, const QString value)
251 QDataStream rs(&reply, QIODevice::WriteOnly);
252 QByteArray cmd = "SET_PROPERTY";
254 rs << expr << id << property << value;
258 void QScriptDebuggerClient::updateWatchData(const WatchData *data)
261 QDataStream rs(&reply, QIODevice::WriteOnly);
262 QByteArray cmd = "EXEC";
264 rs << data->iname << data->name;
268 void QScriptDebuggerClient::executeDebuggerCommand(const QString &command)
271 QDataStream rs(&reply, QIODevice::WriteOnly);
272 QByteArray cmd = "EXEC";
273 QByteArray console = "console";
274 rs << cmd << console << command;
278 void QScriptDebuggerClient::synchronizeWatchers(const QStringList &watchers)
280 // send watchers list
282 QDataStream rs(&reply, QIODevice::WriteOnly);
283 QByteArray cmd = "WATCH_EXPRESSIONS";
289 void QScriptDebuggerClient::expandObject(const QByteArray &iname, quint64 objectId)
292 QDataStream rs(&reply, QIODevice::WriteOnly);
293 QByteArray cmd = "EXPAND";
295 rs << iname << objectId;
299 void QScriptDebuggerClient::sendPing()
303 QDataStream rs(&reply, QIODevice::WriteOnly);
304 QByteArray cmd = "PING";
310 void QScriptDebuggerClient::messageReceived(const QByteArray &data)
312 QByteArray rwData = data;
313 QDataStream stream(&rwData, QIODevice::ReadOnly);
318 if (command == "STOPPED") {
319 d->engine->inferiorSpontaneousStop();
321 QString logString = QString::fromLatin1(command);
323 JSAgentStackFrames stackFrames;
324 QList<WatchData> watches;
325 QList<WatchData> locals;
326 stream >> stackFrames >> watches >> locals;
328 logString += QString::fromLatin1(" (%1 stack frames) (%2 watches) (%3 locals)").
329 arg(stackFrames.size()).arg(watches.size()).arg(locals.size());
331 StackFrames ideStackFrames;
332 for (int i = 0; i != stackFrames.size(); ++i) {
334 frame.line = stackFrames.at(i).lineNumber;
335 frame.function = stackFrames.at(i).functionName;
336 frame.file = d->engine->toFileInProject(QUrl(stackFrames.at(i).fileUrl));
337 frame.usable = QFileInfo(frame.file).isReadable();
339 ideStackFrames << frame;
342 if (ideStackFrames.size() && ideStackFrames.back().function == "<global>")
343 ideStackFrames.takeLast();
344 d->engine->stackHandler()->setFrames(ideStackFrames);
346 d->engine->watchHandler()->beginCycle();
347 bool needPing = false;
349 foreach (WatchData data, watches) {
350 data.iname = d->engine->watchHandler()->watcherName(data.exp);
351 d->engine->watchHandler()->insertData(data);
353 if (d->engine->watchHandler()->expandedINames().contains(data.iname)) {
355 expandObject(data.iname,data.id);
359 foreach (WatchData data, locals) {
360 data.iname = "local." + data.exp;
361 d->engine->watchHandler()->insertData(data);
363 if (d->engine->watchHandler()->expandedINames().contains(data.iname)) {
365 expandObject(data.iname,data.id);
372 d->engine->watchHandler()->endCycle();
375 bool becauseOfException;
376 stream >> becauseOfException;
378 logString += becauseOfException ? " exception" : " no_exception";
380 if (becauseOfException) {
384 logString += QLatin1Char(' ');
386 d->engine->logMessage(QmlEngine::LogReceive, logString);
388 QString msg = stackFrames.isEmpty()
389 ? tr("<p>An uncaught exception occurred:</p><p>%1</p>")
390 .arg(Qt::escape(error))
391 : tr("<p>An uncaught exception occurred in <i>%1</i>:</p><p>%2</p>")
392 .arg(stackFrames.value(0).fileUrl, Qt::escape(error));
393 showMessageBox(QMessageBox::Information, tr("Uncaught Exception"), msg);
396 // Make breakpoint non-pending
402 if (!ideStackFrames.isEmpty()) {
403 file = ideStackFrames.at(0).file;
404 line = ideStackFrames.at(0).line;
405 function = ideStackFrames.at(0).function;
408 BreakHandler *handler = d->engine->breakHandler();
409 foreach (BreakpointModelId id, handler->engineBreakpointIds(d->engine)) {
410 QString processedFilename = handler->fileName(id);
411 if (processedFilename == file && handler->lineNumber(id) == line) {
412 QTC_ASSERT(handler->state(id) == BreakpointInserted,/**/);
413 BreakpointResponse br = handler->response(id);
415 br.lineNumber = line;
416 br.functionName = function;
417 handler->setResponse(id, br);
421 d->engine->logMessage(QmlEngine::LogReceive, logString);
424 if (!ideStackFrames.isEmpty())
425 d->engine->gotoLocation(ideStackFrames.value(0));
427 } else if (command == "RESULT") {
430 stream >> iname >> data;
432 d->engine->logMessage(QmlEngine::LogReceive, QString("%1 %2 %3").arg(QString(command),
433 QString(iname), QString(data.value)));
435 if (iname.startsWith("watch.")) {
436 d->engine->watchHandler()->insertData(data);
437 } else if (iname == "console") {
438 d->engine->showMessage(data.value, ScriptConsoleOutput);
440 qWarning() << "QmlEngine: Unexcpected result: " << iname << data.value;
442 } else if (command == "EXPANDED") {
443 QList<WatchData> result;
445 stream >> iname >> result;
447 d->engine->logMessage(QmlEngine::LogReceive, QString("%1 %2 (%3 x watchdata)").arg(
448 QString(command), QString(iname), QString::number(result.size())));
449 bool needPing = false;
450 foreach (WatchData data, result) {
451 data.iname = iname + '.' + data.exp;
452 d->engine->watchHandler()->insertData(data);
454 if (d->engine->watchHandler()->expandedINames().contains(data.iname)) {
456 expandObject(data.iname, data.id);
461 } else if (command == "LOCALS") {
462 QList<WatchData> locals;
463 QList<WatchData> watches;
465 stream >> frameId >> locals;
466 if (!stream.atEnd()) { // compatibility with jsdebuggeragent from 2.1, 2.2
470 d->engine->logMessage(QmlEngine::LogReceive, QString("%1 %2 (%3 x locals) (%4 x watchdata)").arg(
471 QString(command), QString::number(frameId),
472 QString::number(locals.size()),
473 QString::number(watches.size())));
474 d->engine->watchHandler()->beginCycle();
475 bool needPing = false;
476 foreach (WatchData data, watches) {
477 data.iname = d->engine->watchHandler()->watcherName(data.exp);
478 d->engine->watchHandler()->insertData(data);
480 if (d->engine->watchHandler()->expandedINames().contains(data.iname)) {
482 expandObject(data.iname, data.id);
486 foreach (WatchData data, locals) {
487 data.iname = "local." + data.exp;
488 d->engine->watchHandler()->insertData(data);
489 if (d->engine->watchHandler()->expandedINames().contains(data.iname)) {
491 expandObject(data.iname, data.id);
497 d->engine->watchHandler()->endCycle();
499 } else if (command == "PONG") {
503 d->engine->logMessage(QmlEngine::LogReceive, QString("%1 %2").arg(QString(command), QString::number(ping)));
506 d->engine->watchHandler()->endCycle();
508 qDebug() << Q_FUNC_INFO << "Unknown command: " << command;
509 d->engine->logMessage(QmlEngine::LogReceive, QString("%1 UNKNOWN COMMAND!!").arg(QString(command)));
514 void QScriptDebuggerClient::setEngine(QmlEngine *engine)