1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtSCriptTools module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
42 #include "jsdebuggeragent.h"
43 #include "qt_private/qdeclarativedebughelper_p.h"
45 #include <QtCore/qdatetime.h>
46 #include <QtCore/qdebug.h>
47 #include <QtCore/qurl.h>
48 #include <QtCore/qcoreapplication.h>
49 #include <QtCore/qset.h>
50 #include <QtScript/qscriptcontextinfo.h>
51 #include <QtScript/qscriptengine.h>
52 #include <QtScript/qscriptvalueiterator.h>
54 namespace QmlJSDebugger {
65 struct JSAgentWatchData
75 QDataStream &operator<<(QDataStream &s, const JSAgentWatchData &data)
77 return s << data.exp << data.name << data.value
78 << data.type << data.hasChildren << data.objectId;
81 struct JSAgentStackData
83 QByteArray functionName;
88 QDataStream &operator<<(QDataStream &s, const JSAgentStackData &data)
90 return s << data.functionName << data.fileName << data.lineNumber;
93 struct JSAgentBreakpointData
95 QByteArray functionName;
100 typedef QSet<JSAgentBreakpointData> JSAgentBreakpoints;
102 QDataStream &operator<<(QDataStream &s, const JSAgentBreakpointData &data)
104 return s << data.functionName << data.fileName << data.lineNumber;
107 QDataStream &operator>>(QDataStream &s, JSAgentBreakpointData &data)
109 return s >> data.functionName >> data.fileName >> data.lineNumber;
112 bool operator==(const JSAgentBreakpointData &b1, const JSAgentBreakpointData &b2)
114 return b1.lineNumber == b2.lineNumber && b1.fileName == b2.fileName;
117 uint qHash(const JSAgentBreakpointData &b)
119 return b.lineNumber ^ qHash(b.fileName);
122 class JSDebuggerAgentPrivate
125 JSDebuggerAgentPrivate(JSDebuggerAgent *q)
126 : q(q), state(NoState)
130 void recordKnownObjects(const QList<JSAgentWatchData> &);
131 QList<JSAgentWatchData> getLocals(QScriptContext *);
132 void positionChange(qint64 scriptId, int lineNumber, int columnNumber);
133 QScriptEngine *engine() { return q->engine(); }
135 void messageReceived(const QByteArray &message);
136 void sendMessage(const QByteArray &message) { q->sendMessage(message); }
140 JSDebuggerState state;
145 QHash<qint64, QString> filenames;
146 JSAgentBreakpoints breakpoints;
147 QStringList watchExpressions;
148 QSet<qint64> knownObjectIds;
154 SetupExecEnv(JSDebuggerAgentPrivate *a)
156 previousState(a->state),
157 hadException(a->engine()->hasUncaughtException())
159 agent->state = StoppedState;
164 if (!hadException && agent->engine()->hasUncaughtException())
165 agent->engine()->clearExceptions();
166 agent->state = previousState;
170 JSDebuggerAgentPrivate *agent;
171 JSDebuggerState previousState;
175 static JSAgentWatchData fromScriptValue(const QString &expression,
176 const QScriptValue &value)
178 static const QString arrayStr = QCoreApplication::translate
179 ("Debugger::JSAgentWatchData", "[Array of length %1]");
180 static const QString undefinedStr = QCoreApplication::translate
181 ("Debugger::JSAgentWatchData", "<undefined>");
183 JSAgentWatchData data;
184 data.exp = expression.toUtf8();
185 data.name = data.exp;
186 data.hasChildren = false;
187 data.value = value.toString().toUtf8();
188 data.objectId = value.objectId();
189 if (value.isArray()) {
191 data.value = arrayStr.arg(value.property("length").toString()).toUtf8();
192 data.hasChildren = true;
193 } else if (value.isBool()) {
195 // data.value = value.toBool() ? "true" : "false";
196 } else if (value.isDate()) {
198 data.value = value.toDateTime().toString().toUtf8();
199 } else if (value.isError()) {
201 } else if (value.isFunction()) {
202 data.type = "Function";
203 } else if (value.isUndefined()) {
204 data.type = undefinedStr.toUtf8();
205 } else if (value.isNumber()) {
206 data.type = "Number";
207 } else if (value.isRegExp()) {
208 data.type = "RegExp";
209 } else if (value.isString()) {
210 data.type = "String";
211 } else if (value.isVariant()) {
212 data.type = "Variant";
213 } else if (value.isQObject()) {
214 const QObject *obj = value.toQObject();
215 data.type = "Object";
217 data.value += obj->metaObject()->className();
219 data.hasChildren = true;
220 } else if (value.isObject()) {
221 data.type = "Object";
222 data.hasChildren = true;
223 data.value = "[Object]";
224 } else if (value.isNull()) {
225 data.type = "<null>";
227 data.type = "<unknown>";
232 static QList<JSAgentWatchData> expandObject(const QScriptValue &object)
234 QList<JSAgentWatchData> result;
235 QScriptValueIterator it(object);
236 QByteArray expPrefix = '@' + QByteArray::number(object.objectId(), 16) + "->";
237 while (it.hasNext()) {
239 if (it.flags() & QScriptValue::SkipInEnumeration)
241 if (/*object.isQObject() &&*/ it.value().isFunction()) {
242 // Cosmetics: skip all functions and slot, there are too many of them,
243 // and it is not useful information in the debugger.
246 JSAgentWatchData data = fromScriptValue(it.name(), it.value());
247 data.exp.prepend(expPrefix);
250 if (result.isEmpty()) {
251 JSAgentWatchData data;
252 data.name = "<no initialized data>";
253 data.hasChildren = false;
255 data.exp.prepend(expPrefix);
262 void JSDebuggerAgentPrivate::recordKnownObjects(const QList<JSAgentWatchData>& list)
264 foreach (const JSAgentWatchData &data, list)
265 knownObjectIds << data.objectId;
268 QList<JSAgentWatchData> JSDebuggerAgentPrivate::getLocals(QScriptContext *ctx)
270 QList<JSAgentWatchData> locals;
272 QScriptValue activationObject = ctx->activationObject();
273 QScriptValue thisObject = ctx->thisObject();
274 locals = expandObject(activationObject);
275 if (thisObject.isObject()
276 && thisObject.objectId() != engine()->globalObject().objectId())
277 locals.prepend(fromScriptValue("this", thisObject));
278 recordKnownObjects(locals);
279 knownObjectIds << activationObject.objectId();
285 Constructs a new agent for the given \a engine. The agent will
286 report debugging-related events (e.g. step completion) to the given
289 JSDebuggerAgent::JSDebuggerAgent(QScriptEngine *engine)
290 : QDeclarativeDebugService("JSDebugger")
291 , QScriptEngineAgent(engine)
292 , d(new JSDebuggerAgentPrivate(this))
295 JSDebuggerAgent::JSDebuggerAgent(QDeclarativeEngine *engine)
296 : QDeclarativeDebugService("JSDebugger")
297 , QScriptEngineAgent(QDeclarativeDebugHelper::getScriptEngine(engine))
298 , d(new JSDebuggerAgentPrivate(this))
302 Destroys this QScriptDebuggerAgent.
304 JSDebuggerAgent::~JSDebuggerAgent()
312 void JSDebuggerAgent::scriptLoad(qint64 id, const QString &program,
313 const QString &fileName, int)
316 d->filenames.insert(id, QUrl(fileName).toLocalFile());
322 void JSDebuggerAgent::scriptUnload(qint64 id)
324 d->filenames.remove(id);
330 void JSDebuggerAgent::contextPush()
337 void JSDebuggerAgent::contextPop()
344 void JSDebuggerAgent::functionEntry(qint64 scriptId)
353 void JSDebuggerAgent::functionExit(qint64 scriptId, const QScriptValue &returnValue)
356 Q_UNUSED(returnValue);
363 void JSDebuggerAgent::positionChange(qint64 scriptId, int lineNumber, int columnNumber)
365 d->positionChange(scriptId, lineNumber, columnNumber);
368 void JSDebuggerAgentPrivate::positionChange(qint64 scriptId, int lineNumber, int columnNumber)
370 Q_UNUSED(columnNumber);
372 if (state == StoppedState)
373 return; //no re-entrency
376 if (!breakpoints.isEmpty()) {
377 QHash<qint64, QString>::const_iterator it = filenames.constFind(scriptId);
378 QScriptContext *ctx = engine()->currentContext();
379 QScriptContextInfo info(ctx);
380 if (it == filenames.constEnd()) {
381 // It is possible that the scripts are loaded before the agent is attached
382 QString filename = QUrl(info.fileName()).toLocalFile();
384 JSAgentStackData frame;
385 frame.functionName = info.functionName().toUtf8();
387 QPair<QString, qint32> key = qMakePair(filename, lineNumber);
388 it = filenames.insert(scriptId, filename);
390 JSAgentBreakpointData bp;
391 bp.fileName = it->toUtf8();
392 bp.lineNumber = lineNumber;
393 if (breakpoints.contains(bp)) {
404 case SteppingOutState:
408 case SteppingOverState:
412 case SteppingIntoState:
422 void JSDebuggerAgent::exceptionThrow(qint64 scriptId,
423 const QScriptValue &exception,
428 Q_UNUSED(hasHandler);
429 // qDebug() << Q_FUNC_INFO << exception.toString() << hasHandler;
430 #if 0 //sometimes, we get exceptions that we should just ignore.
431 if (!hasHandler && state != StoppedState)
432 stopped(true, exception);
439 void JSDebuggerAgent::exceptionCatch(qint64 scriptId, const QScriptValue &exception)
445 bool JSDebuggerAgent::supportsExtension(Extension extension) const
447 return extension == QScriptEngineAgent::DebuggerInvocationRequest;
450 QVariant JSDebuggerAgent::extension(Extension extension, const QVariant &argument)
452 if (extension == QScriptEngineAgent::DebuggerInvocationRequest) {
456 return QScriptEngineAgent::extension(extension, argument);
459 void JSDebuggerAgent::messageReceived(const QByteArray &message)
461 d->messageReceived(message);
464 void JSDebuggerAgentPrivate::messageReceived(const QByteArray &message)
466 QDataStream ds(message);
469 if (command == "BREAKPOINTS") {
471 //qDebug() << "BREAKPOINTS";
472 //foreach (const JSAgentBreakpointData &bp, breakpoints)
473 // qDebug() << "BREAKPOINT: " << bp.fileName << bp.lineNumber;
474 } else if (command == "WATCH_EXPRESSIONS") {
475 ds >> watchExpressions;
476 } else if (command == "STEPOVER") {
478 state = SteppingOverState;
480 } else if (command == "STEPINTO" || command == "INTERRUPT") {
482 state = SteppingIntoState;
484 } else if (command == "STEPOUT") {
486 state = SteppingOutState;
488 } else if (command == "CONTINUE") {
491 } else if (command == "EXEC") {
492 SetupExecEnv execEnv(this);
498 JSAgentWatchData data = fromScriptValue(expr, engine()->evaluate(expr));
499 knownObjectIds << data.objectId;
502 QDataStream rs(&reply, QIODevice::WriteOnly);
503 rs << QByteArray("RESULT") << id << data;
505 } else if (command == "EXPAND") {
506 SetupExecEnv execEnv(this);
508 QByteArray requestId;
510 ds >> requestId >> objectId;
512 if (knownObjectIds.contains(objectId))
513 v = engine()->objectById(objectId);
515 QList<JSAgentWatchData> result = expandObject(v);
516 recordKnownObjects(result);
519 QDataStream rs(&reply, QIODevice::WriteOnly);
520 rs << QByteArray("EXPANDED") << requestId << result;
523 } else if (command == "ACTIVATE_FRAME") {
524 SetupExecEnv execEnv(this);
530 QScriptContext *ctx = engine()->currentContext();
531 while (ctx && deep < frameId) {
532 ctx = ctx->parentContext();
536 QList<JSAgentWatchData> locals = getLocals(ctx);
539 QDataStream rs(&reply, QIODevice::WriteOnly);
540 rs << QByteArray("LOCALS") << frameId << locals;
542 } else if (command == "SET_PROPERTY") {
543 SetupExecEnv execEnv(this);
549 ds >> id >> objectId >> property >> value;
551 if (knownObjectIds.contains(objectId)) {
553 object = engine()->objectById(objectId);
555 if (object.isObject()) {
556 QScriptValue result = engine()->evaluate(value);
557 object.setProperty(property, result);
562 } else if (command == "PING") {
566 QDataStream rs(&reply, QIODevice::WriteOnly);
567 rs << QByteArray("PONG") << ping;
570 qDebug() << Q_FUNC_INFO << "Unknown command" << command;
573 q->baseMessageReceived(message);
576 void JSDebuggerAgentPrivate::stopped()
578 bool becauseOfException = false;
579 const QScriptValue &exception = QScriptValue();
581 knownObjectIds.clear();
582 state = StoppedState;
583 QList<JSAgentStackData> backtrace;
585 for (QScriptContext* ctx = engine()->currentContext(); ctx; ctx = ctx->parentContext()) {
586 QScriptContextInfo info(ctx);
588 JSAgentStackData frame;
589 frame.functionName = info.functionName().toUtf8();
590 if (frame.functionName.isEmpty()) {
591 if (ctx->parentContext()) {
592 switch (info.functionType()) {
593 case QScriptContextInfo::ScriptFunction:
594 frame.functionName = "<anonymous>";
596 case QScriptContextInfo::NativeFunction:
597 frame.functionName = "<native>";
599 case QScriptContextInfo::QtFunction:
600 case QScriptContextInfo::QtPropertyFunction:
601 frame.functionName = "<native slot>";
605 frame.functionName = "<global>";
608 frame.lineNumber = info.lineNumber();
609 // if the line number is unknown, fallback to the function line number
610 if (frame.lineNumber == -1)
611 frame.lineNumber = info.functionStartLineNumber();
612 frame.fileName = QUrl(info.fileName()).toLocalFile().toUtf8();
613 backtrace.append(frame);
615 QList<JSAgentWatchData> watches;
616 foreach (const QString &expr, watchExpressions)
617 watches << fromScriptValue(expr, engine()->evaluate(expr));
618 recordKnownObjects(watches);
620 QList<JSAgentWatchData> locals = getLocals(engine()->currentContext());
622 if (!becauseOfException) {
623 // Clear any exceptions occurred during locals evaluation.
624 engine()->clearExceptions();
628 QDataStream rs(&reply, QIODevice::WriteOnly);
629 rs << QByteArray("STOPPED") << backtrace << watches << locals
630 << becauseOfException << exception.toString();
633 loop.exec(QEventLoop::ExcludeUserInputEvents);
636 void JSDebuggerAgentPrivate::continueExec()
641 void JSDebuggerAgent::statusChanged(Status status)
643 engine()->setAgent((status == QDeclarativeDebugService::Enabled) ? this : 0);
646 void JSDebuggerAgent::baseMessageReceived(const QByteArray &message)
648 QDeclarativeDebugService::messageReceived(message);
651 } // namespace QmlJSDebugger