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::activateFrame(int index)
198 QDataStream rs(&reply, QIODevice::WriteOnly);
199 QByteArray cmd = "ACTIVATE_FRAME";
205 void QScriptDebuggerClient::insertBreakpoints(BreakHandler *handler, BreakpointModelId *id)
207 JSAgentBreakpointData bp;
208 bp.fileUrl = QUrl::fromLocalFile(handler->fileName(*id)).toString().toUtf8();
209 bp.lineNumber = handler->lineNumber(*id);
210 bp.functionName = handler->functionName(*id).toUtf8();
211 d->breakpoints.insert(bp);
214 void QScriptDebuggerClient::removeBreakpoints(BreakpointModelId */*id*/)
219 void QScriptDebuggerClient::setBreakpoints()
222 QDataStream rs(&reply, QIODevice::WriteOnly);
223 QByteArray cmd = "BREAKPOINTS";
227 QStringList breakPointsStr;
228 foreach (const JSAgentBreakpointData &bp, d->breakpoints) {
229 breakPointsStr << QString("('%1' '%2' %3)").arg(QString(bp.functionName),
230 QString(bp.fileUrl), QString::number(bp.lineNumber));
235 d->breakpoints.clear();
238 void QScriptDebuggerClient::assignValueInDebugger(const QByteArray expr, const quint64 &id,
239 const QString &property, const QString value)
242 QDataStream rs(&reply, QIODevice::WriteOnly);
243 QByteArray cmd = "SET_PROPERTY";
245 rs << expr << id << property << value;
249 void QScriptDebuggerClient::updateWatchData(const WatchData *data)
252 QDataStream rs(&reply, QIODevice::WriteOnly);
253 QByteArray cmd = "EXEC";
255 rs << data->iname << data->name;
259 void QScriptDebuggerClient::executeDebuggerCommand(const QString &command)
262 QDataStream rs(&reply, QIODevice::WriteOnly);
263 QByteArray cmd = "EXEC";
264 QByteArray console = "console";
265 rs << cmd << console << command;
269 void QScriptDebuggerClient::synchronizeWatchers(const QStringList &watchers)
271 // send watchers list
273 QDataStream rs(&reply, QIODevice::WriteOnly);
274 QByteArray cmd = "WATCH_EXPRESSIONS";
280 void QScriptDebuggerClient::expandObject(const QByteArray &iname, quint64 objectId)
283 QDataStream rs(&reply, QIODevice::WriteOnly);
284 QByteArray cmd = "EXPAND";
286 rs << iname << objectId;
290 void QScriptDebuggerClient::sendPing()
294 QDataStream rs(&reply, QIODevice::WriteOnly);
295 QByteArray cmd = "PING";
301 void QScriptDebuggerClient::messageReceived(const QByteArray &data)
303 QByteArray rwData = data;
304 QDataStream stream(&rwData, QIODevice::ReadOnly);
309 if (command == "STOPPED") {
310 d->engine->inferiorSpontaneousStop();
312 QString logString = QString::fromLatin1(command);
314 JSAgentStackFrames stackFrames;
315 QList<WatchData> watches;
316 QList<WatchData> locals;
317 stream >> stackFrames >> watches >> locals;
319 logString += QString::fromLatin1(" (%1 stack frames) (%2 watches) (%3 locals)").
320 arg(stackFrames.size()).arg(watches.size()).arg(locals.size());
322 StackFrames ideStackFrames;
323 for (int i = 0; i != stackFrames.size(); ++i) {
325 frame.line = stackFrames.at(i).lineNumber;
326 frame.function = stackFrames.at(i).functionName;
327 frame.file = d->engine->toFileInProject(QUrl(stackFrames.at(i).fileUrl));
328 frame.usable = QFileInfo(frame.file).isReadable();
330 ideStackFrames << frame;
333 if (ideStackFrames.size() && ideStackFrames.back().function == "<global>")
334 ideStackFrames.takeLast();
335 d->engine->stackHandler()->setFrames(ideStackFrames);
337 d->engine->watchHandler()->beginCycle();
338 bool needPing = false;
340 foreach (WatchData data, watches) {
341 data.iname = d->engine->watchHandler()->watcherName(data.exp);
342 d->engine->watchHandler()->insertData(data);
344 if (d->engine->watchHandler()->expandedINames().contains(data.iname)) {
346 expandObject(data.iname,data.id);
350 foreach (WatchData data, locals) {
351 data.iname = "local." + data.exp;
352 d->engine->watchHandler()->insertData(data);
354 if (d->engine->watchHandler()->expandedINames().contains(data.iname)) {
356 expandObject(data.iname,data.id);
363 d->engine->watchHandler()->endCycle();
366 bool becauseOfException;
367 stream >> becauseOfException;
369 logString += becauseOfException ? " exception" : " no_exception";
371 if (becauseOfException) {
375 logString += QLatin1Char(' ');
377 d->engine->logMessage(QmlEngine::LogReceive, logString);
379 QString msg = stackFrames.isEmpty()
380 ? tr("<p>An uncaught exception occurred:</p><p>%1</p>")
381 .arg(Qt::escape(error))
382 : tr("<p>An uncaught exception occurred in <i>%1</i>:</p><p>%2</p>")
383 .arg(stackFrames.value(0).fileUrl, Qt::escape(error));
384 showMessageBox(QMessageBox::Information, tr("Uncaught Exception"), msg);
387 // Make breakpoint non-pending
393 if (!ideStackFrames.isEmpty()) {
394 file = ideStackFrames.at(0).file;
395 line = ideStackFrames.at(0).line;
396 function = ideStackFrames.at(0).function;
399 BreakHandler *handler = d->engine->breakHandler();
400 foreach (BreakpointModelId id, handler->engineBreakpointIds(d->engine)) {
401 QString processedFilename = handler->fileName(id);
402 if (processedFilename == file && handler->lineNumber(id) == line) {
403 QTC_ASSERT(handler->state(id) == BreakpointInserted,/**/);
404 BreakpointResponse br = handler->response(id);
406 br.lineNumber = line;
407 br.functionName = function;
408 handler->setResponse(id, br);
412 d->engine->logMessage(QmlEngine::LogReceive, logString);
415 if (!ideStackFrames.isEmpty())
416 d->engine->gotoLocation(ideStackFrames.value(0));
418 } else if (command == "RESULT") {
421 stream >> iname >> data;
423 d->engine->logMessage(QmlEngine::LogReceive, QString("%1 %2 %3").arg(QString(command),
424 QString(iname), QString(data.value)));
426 if (iname.startsWith("watch.")) {
427 d->engine->watchHandler()->insertData(data);
428 } else if (iname == "console") {
429 d->engine->showMessage(data.value, ScriptConsoleOutput);
431 qWarning() << "QmlEngine: Unexcpected result: " << iname << data.value;
433 } else if (command == "EXPANDED") {
434 QList<WatchData> result;
436 stream >> iname >> result;
438 d->engine->logMessage(QmlEngine::LogReceive, QString("%1 %2 (%3 x watchdata)").arg(
439 QString(command), QString(iname), QString::number(result.size())));
440 bool needPing = false;
441 foreach (WatchData data, result) {
442 data.iname = iname + '.' + data.exp;
443 d->engine->watchHandler()->insertData(data);
445 if (d->engine->watchHandler()->expandedINames().contains(data.iname)) {
447 expandObject(data.iname, data.id);
452 } else if (command == "LOCALS") {
453 QList<WatchData> locals;
454 QList<WatchData> watches;
456 stream >> frameId >> locals;
457 if (!stream.atEnd()) { // compatibility with jsdebuggeragent from 2.1, 2.2
461 d->engine->logMessage(QmlEngine::LogReceive, QString("%1 %2 (%3 x locals) (%4 x watchdata)").arg(
462 QString(command), QString::number(frameId),
463 QString::number(locals.size()),
464 QString::number(watches.size())));
465 d->engine->watchHandler()->beginCycle();
466 bool needPing = false;
467 foreach (WatchData data, watches) {
468 data.iname = d->engine->watchHandler()->watcherName(data.exp);
469 d->engine->watchHandler()->insertData(data);
471 if (d->engine->watchHandler()->expandedINames().contains(data.iname)) {
473 expandObject(data.iname, data.id);
477 foreach (WatchData data, locals) {
478 data.iname = "local." + data.exp;
479 d->engine->watchHandler()->insertData(data);
480 if (d->engine->watchHandler()->expandedINames().contains(data.iname)) {
482 expandObject(data.iname, data.id);
488 d->engine->watchHandler()->endCycle();
490 } else if (command == "PONG") {
494 d->engine->logMessage(QmlEngine::LogReceive, QString("%1 %2").arg(QString(command), QString::number(ping)));
497 d->engine->watchHandler()->endCycle();
499 qDebug() << Q_FUNC_INFO << "Unknown command: " << command;
500 d->engine->logMessage(QmlEngine::LogReceive, QString("%1 UNKNOWN COMMAND!!").arg(QString(command)));
505 void QScriptDebuggerClient::setEngine(QmlEngine *engine)