OSDN Git Service

Delegate javascript debugging to Script and V8 debugger clients.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / debugger / qml / qscriptdebuggerclient.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
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.
18 **
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.
22 **
23 ** Other Usage
24 **
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.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at info@qt.nokia.com.
30 **
31 **************************************************************************/
32 #include "qscriptdebuggerclient.h"
33
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"
42
43 #include <QTextDocument>
44 #include <QFileInfo>
45 #include <QMessageBox>
46 #include <extensionsystem/pluginmanager.h>
47 #include <utils/qtcassert.h>
48
49 namespace Debugger {
50 namespace Internal {
51
52 struct JSAgentBreakpointData
53 {
54     QByteArray functionName;
55     QByteArray fileUrl;
56     qint32 lineNumber;
57 };
58
59 struct JSAgentStackData
60 {
61     QByteArray functionName;
62     QByteArray fileUrl;
63     qint32 lineNumber;
64 };
65
66 uint qHash(const JSAgentBreakpointData &b)
67 {
68     return b.lineNumber ^ qHash(b.fileUrl);
69 }
70
71 QDataStream &operator<<(QDataStream &s, const JSAgentBreakpointData &data)
72 {
73     return s << data.functionName << data.fileUrl << data.lineNumber;
74 }
75
76 QDataStream &operator<<(QDataStream &s, const JSAgentStackData &data)
77 {
78     return s << data.functionName << data.fileUrl << data.lineNumber;
79 }
80
81 QDataStream &operator>>(QDataStream &s, JSAgentBreakpointData &data)
82 {
83     return s >> data.functionName >> data.fileUrl >> data.lineNumber;
84 }
85
86 QDataStream &operator>>(QDataStream &s, JSAgentStackData &data)
87 {
88     return s >> data.functionName >> data.fileUrl >> data.lineNumber;
89 }
90
91 bool operator==(const JSAgentBreakpointData &b1, const JSAgentBreakpointData &b2)
92 {
93     return b1.lineNumber == b2.lineNumber && b1.fileUrl == b2.fileUrl;
94 }
95
96 typedef QSet<JSAgentBreakpointData> JSAgentBreakpoints;
97 typedef QList<JSAgentStackData> JSAgentStackFrames;
98
99
100 static QDataStream &operator>>(QDataStream &s, WatchData &data)
101 {
102     data = WatchData();
103     QByteArray name;
104     QByteArray value;
105     QByteArray type;
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();
113     return s;
114 }
115
116 class QScriptDebuggerClientPrivate
117 {
118 public:
119     explicit QScriptDebuggerClientPrivate(QScriptDebuggerClient *) :
120         ping(0), engine(0)
121     {
122
123     }
124
125     int ping;
126     QmlEngine *engine;
127     JSAgentBreakpoints breakpoints;
128 };
129
130 QScriptDebuggerClient::QScriptDebuggerClient(QmlJsDebugClient::QDeclarativeDebugConnection* client)
131     : QmlDebuggerClient(client, QLatin1String("JSDebugger")),
132       d(new QScriptDebuggerClientPrivate(this))
133 {
134 }
135
136 QScriptDebuggerClient::~QScriptDebuggerClient()
137 {
138     delete d;
139 }
140
141 void QScriptDebuggerClient::executeStep()
142 {
143     QByteArray reply;
144     QDataStream rs(&reply, QIODevice::WriteOnly);
145     QByteArray cmd = "STEPINTO";
146     rs << cmd;
147     sendMessage(reply);
148 }
149
150 void QScriptDebuggerClient::executeStepOut()
151 {
152     QByteArray reply;
153     QDataStream rs(&reply, QIODevice::WriteOnly);
154     QByteArray cmd = "STEPOUT";
155     rs << cmd;
156     sendMessage(reply);
157 }
158
159 void QScriptDebuggerClient::executeNext()
160 {
161     QByteArray reply;
162     QDataStream rs(&reply, QIODevice::WriteOnly);
163     QByteArray cmd = "STEPOVER";
164     rs << cmd;
165     sendMessage(reply);
166 }
167
168 void QScriptDebuggerClient::executeStepI()
169 {
170     QByteArray reply;
171     QDataStream rs(&reply, QIODevice::WriteOnly);
172     QByteArray cmd = "STEPINTO";
173     rs << cmd;
174     sendMessage(reply);
175 }
176
177 void QScriptDebuggerClient::continueInferior()
178 {
179     QByteArray reply;
180     QDataStream rs(&reply, QIODevice::WriteOnly);
181     QByteArray cmd = "CONTINUE";
182     rs << cmd;
183     sendMessage(reply);
184 }
185
186 void QScriptDebuggerClient::interruptInferior()
187 {
188     QByteArray reply;
189     QDataStream rs(&reply, QIODevice::WriteOnly);
190     QByteArray cmd = "INTERRUPT";
191     rs << cmd;
192     sendMessage(reply);
193 }
194
195 void QScriptDebuggerClient::activateFrame(int index)
196 {
197     QByteArray reply;
198     QDataStream rs(&reply, QIODevice::WriteOnly);
199     QByteArray cmd = "ACTIVATE_FRAME";
200     rs << cmd
201        << index;
202     sendMessage(reply);
203 }
204
205 void QScriptDebuggerClient::insertBreakpoints(BreakHandler *handler, BreakpointModelId *id)
206 {
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);
212 }
213
214 void QScriptDebuggerClient::removeBreakpoints(BreakpointModelId */*id*/)
215 {
216
217 }
218
219 void QScriptDebuggerClient::setBreakpoints()
220 {
221     QByteArray reply;
222     QDataStream rs(&reply, QIODevice::WriteOnly);
223     QByteArray cmd = "BREAKPOINTS";
224     rs << cmd
225        << d->breakpoints;
226
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));
231     }
232
233     sendMessage(reply);
234
235     d->breakpoints.clear();
236 }
237
238 void QScriptDebuggerClient::assignValueInDebugger(const QByteArray expr, const quint64 &id,
239                                                   const QString &property, const QString value)
240 {
241     QByteArray reply;
242     QDataStream rs(&reply, QIODevice::WriteOnly);
243     QByteArray cmd = "SET_PROPERTY";
244     rs << cmd;
245     rs << expr << id << property << value;
246     sendMessage(reply);
247 }
248
249 void QScriptDebuggerClient::updateWatchData(const WatchData *data)
250 {
251     QByteArray reply;
252     QDataStream rs(&reply, QIODevice::WriteOnly);
253     QByteArray cmd = "EXEC";
254     rs << cmd;
255     rs << data->iname << data->name;
256     sendMessage(reply);
257 }
258
259 void QScriptDebuggerClient::executeDebuggerCommand(const QString &command)
260 {
261     QByteArray reply;
262     QDataStream rs(&reply, QIODevice::WriteOnly);
263     QByteArray cmd = "EXEC";
264     QByteArray console = "console";
265     rs << cmd << console << command;
266     sendMessage(reply);
267 }
268
269 void QScriptDebuggerClient::synchronizeWatchers(const QStringList &watchers)
270 {
271     // send watchers list
272     QByteArray reply;
273     QDataStream rs(&reply, QIODevice::WriteOnly);
274     QByteArray cmd = "WATCH_EXPRESSIONS";
275     rs << cmd;
276     rs << watchers;
277     sendMessage(reply);
278 }
279
280 void QScriptDebuggerClient::expandObject(const QByteArray &iname, quint64 objectId)
281 {
282     QByteArray reply;
283     QDataStream rs(&reply, QIODevice::WriteOnly);
284     QByteArray cmd = "EXPAND";
285     rs << cmd;
286     rs << iname << objectId;
287     sendMessage(reply);
288 }
289
290 void QScriptDebuggerClient::sendPing()
291 {
292     d->ping++;
293     QByteArray reply;
294     QDataStream rs(&reply, QIODevice::WriteOnly);
295     QByteArray cmd = "PING";
296     rs << cmd;
297     rs << d->ping;
298     sendMessage(reply);
299 }
300
301 void QScriptDebuggerClient::messageReceived(const QByteArray &data)
302 {
303     QByteArray rwData = data;
304     QDataStream stream(&rwData, QIODevice::ReadOnly);
305
306     QByteArray command;
307     stream >> command;
308
309     if (command == "STOPPED") {
310         d->engine->inferiorSpontaneousStop();
311
312         QString logString = QString::fromLatin1(command);
313
314         JSAgentStackFrames stackFrames;
315         QList<WatchData> watches;
316         QList<WatchData> locals;
317         stream >> stackFrames >> watches >> locals;
318
319         logString += QString::fromLatin1(" (%1 stack frames) (%2 watches)  (%3 locals)").
320                 arg(stackFrames.size()).arg(watches.size()).arg(locals.size());
321
322         StackFrames ideStackFrames;
323         for (int i = 0; i != stackFrames.size(); ++i) {
324             StackFrame frame;
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();
329             frame.level = i + 1;
330             ideStackFrames << frame;
331         }
332
333         if (ideStackFrames.size() && ideStackFrames.back().function == "<global>")
334             ideStackFrames.takeLast();
335         d->engine->stackHandler()->setFrames(ideStackFrames);
336
337         d->engine->watchHandler()->beginCycle();
338         bool needPing = false;
339
340         foreach (WatchData data, watches) {
341             data.iname = d->engine->watchHandler()->watcherName(data.exp);
342             d->engine->watchHandler()->insertData(data);
343
344             if (d->engine->watchHandler()->expandedINames().contains(data.iname)) {
345                 needPing = true;
346                 expandObject(data.iname,data.id);
347             }
348         }
349
350         foreach (WatchData data, locals) {
351             data.iname = "local." + data.exp;
352             d->engine->watchHandler()->insertData(data);
353
354             if (d->engine->watchHandler()->expandedINames().contains(data.iname)) {
355                 needPing = true;
356                 expandObject(data.iname,data.id);
357             }
358         }
359
360         if (needPing) {
361             sendPing();
362         } else {
363             d->engine->watchHandler()->endCycle();
364         }
365
366         bool becauseOfException;
367         stream >> becauseOfException;
368
369         logString += becauseOfException ? " exception" : " no_exception";
370
371         if (becauseOfException) {
372             QString error;
373             stream >> error;
374
375             logString += QLatin1Char(' ');
376             logString += error;
377             d->engine->logMessage(QmlEngine::LogReceive, logString);
378
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);
385         } else {
386             //
387             // Make breakpoint non-pending
388             //
389             QString file;
390             QString function;
391             int line = -1;
392
393             if (!ideStackFrames.isEmpty()) {
394                 file = ideStackFrames.at(0).file;
395                 line = ideStackFrames.at(0).line;
396                 function = ideStackFrames.at(0).function;
397             }
398
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);
405                     br.fileName = file;
406                     br.lineNumber = line;
407                     br.functionName = function;
408                     handler->setResponse(id, br);
409                 }
410             }
411
412             d->engine->logMessage(QmlEngine::LogReceive, logString);
413         }
414
415         if (!ideStackFrames.isEmpty())
416             d->engine->gotoLocation(ideStackFrames.value(0));
417
418     } else if (command == "RESULT") {
419         WatchData data;
420         QByteArray iname;
421         stream >> iname >> data;
422
423         d->engine->logMessage(QmlEngine::LogReceive, QString("%1 %2 %3").arg(QString(command),
424                                                                              QString(iname), QString(data.value)));
425         data.iname = iname;
426         if (iname.startsWith("watch.")) {
427             d->engine->watchHandler()->insertData(data);
428         } else if (iname == "console") {
429             d->engine->showMessage(data.value, ScriptConsoleOutput);
430         } else {
431             qWarning() << "QmlEngine: Unexcpected result: " << iname << data.value;
432         }
433     } else if (command == "EXPANDED") {
434         QList<WatchData> result;
435         QByteArray iname;
436         stream >> iname >> result;
437
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);
444
445             if (d->engine->watchHandler()->expandedINames().contains(data.iname)) {
446                 needPing = true;
447                 expandObject(data.iname, data.id);
448             }
449         }
450         if (needPing)
451             sendPing();
452     } else if (command == "LOCALS") {
453         QList<WatchData> locals;
454         QList<WatchData> watches;
455         int frameId;
456         stream >> frameId >> locals;
457         if (!stream.atEnd()) { // compatibility with jsdebuggeragent from 2.1, 2.2
458             stream >> watches;
459         }
460
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);
470
471             if (d->engine->watchHandler()->expandedINames().contains(data.iname)) {
472                 needPing = true;
473                 expandObject(data.iname, data.id);
474             }
475         }
476
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)) {
481                 needPing = true;
482                 expandObject(data.iname, data.id);
483             }
484         }
485         if (needPing)
486             sendPing();
487         else
488             d->engine->watchHandler()->endCycle();
489
490     } else if (command == "PONG") {
491         int ping;
492         stream >> ping;
493
494         d->engine->logMessage(QmlEngine::LogReceive, QString("%1 %2").arg(QString(command), QString::number(ping)));
495
496         if (ping == d->ping)
497             d->engine->watchHandler()->endCycle();
498     } else {
499         qDebug() << Q_FUNC_INFO << "Unknown command: " << command;
500         d->engine->logMessage(QmlEngine::LogReceive, QString("%1 UNKNOWN COMMAND!!").arg(QString(command)));
501     }
502
503 }
504
505 void QScriptDebuggerClient::setEngine(QmlEngine *engine)
506 {
507     d->engine = engine;
508 }
509
510 } // Internal
511 } // Debugger