| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). |
| ** All rights reserved. |
| ** Contact: Nokia Corporation (qt-info@nokia.com) |
| ** |
| ** This file is part of the QtSCriptTools module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** GNU Lesser General Public License Usage |
| ** This file may be used under the terms of the GNU Lesser General Public |
| ** License version 2.1 as published by the Free Software Foundation and |
| ** appearing in the file LICENSE.LGPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU Lesser |
| ** General Public License version 2.1 requirements will be met: |
| ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
| ** |
| ** In addition, as a special exception, Nokia gives you certain additional |
| ** rights. These rights are described in the Nokia Qt LGPL Exception |
| ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU General |
| ** Public License version 3.0 as published by the Free Software Foundation |
| ** and appearing in the file LICENSE.GPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU General |
| ** Public License version 3.0 requirements will be met: |
| ** http://www.gnu.org/copyleft/gpl.html. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qscriptdebuggercommandexecutor_p.h" |
| |
| #include "qscriptdebuggerbackend_p.h" |
| #include "qscriptdebuggercommand_p.h" |
| #include "qscriptdebuggerresponse_p.h" |
| #include "qscriptdebuggervalue_p.h" |
| #include "qscriptdebuggervalueproperty_p.h" |
| #include "qscriptbreakpointdata_p.h" |
| #include "qscriptobjectsnapshot_p.h" |
| #include "qscriptdebuggerobjectsnapshotdelta_p.h" |
| |
| #include <QtCore/qstringlist.h> |
| #include <QtScript/qscriptengine.h> |
| #include <QtScript/qscriptcontextinfo.h> |
| #include <QtScript/qscriptvalueiterator.h> |
| #include <QtCore/qdebug.h> |
| |
| Q_DECLARE_METATYPE(QScriptScriptsDelta) |
| Q_DECLARE_METATYPE(QScriptDebuggerValueProperty) |
| Q_DECLARE_METATYPE(QScriptDebuggerValuePropertyList) |
| Q_DECLARE_METATYPE(QScriptDebuggerObjectSnapshotDelta) |
| |
| QT_BEGIN_NAMESPACE |
| |
| /*! |
| \since 4.5 |
| \class QScriptDebuggerCommandExecutor |
| \internal |
| |
| \brief The QScriptDebuggerCommandExecutor applies debugger commands to a back-end. |
| |
| The execute() function takes a command (typically produced by a |
| QScriptDebuggerFrontend) and applies it to a QScriptDebuggerBackend. |
| |
| \sa QScriptDebuggerCommmand |
| */ |
| |
| class QScriptDebuggerCommandExecutorPrivate |
| { |
| public: |
| QScriptDebuggerCommandExecutorPrivate(); |
| ~QScriptDebuggerCommandExecutorPrivate(); |
| }; |
| |
| QScriptDebuggerCommandExecutorPrivate::QScriptDebuggerCommandExecutorPrivate() |
| { |
| } |
| |
| QScriptDebuggerCommandExecutorPrivate::~QScriptDebuggerCommandExecutorPrivate() |
| { |
| } |
| |
| QScriptDebuggerCommandExecutor::QScriptDebuggerCommandExecutor() |
| : d_ptr(new QScriptDebuggerCommandExecutorPrivate()) |
| { |
| } |
| |
| QScriptDebuggerCommandExecutor::~QScriptDebuggerCommandExecutor() |
| { |
| } |
| |
| static bool isPrefixOf(const QString &prefix, const QString &what) |
| { |
| return ((what.length() > prefix.length()) |
| && what.startsWith(prefix)); |
| } |
| |
| /*! |
| Applies the given \a command to the given \a backend. |
| */ |
| QScriptDebuggerResponse QScriptDebuggerCommandExecutor::execute( |
| QScriptDebuggerBackend *backend, |
| const QScriptDebuggerCommand &command) |
| { |
| QScriptDebuggerResponse response; |
| switch (command.type()) { |
| case QScriptDebuggerCommand::None: |
| break; |
| |
| case QScriptDebuggerCommand::Interrupt: |
| backend->interruptEvaluation(); |
| break; |
| |
| case QScriptDebuggerCommand::Continue: |
| if (backend->engine()->isEvaluating()) { |
| backend->continueEvalution(); |
| response.setAsync(true); |
| } |
| break; |
| |
| case QScriptDebuggerCommand::StepInto: { |
| QVariant attr = command.attribute(QScriptDebuggerCommand::StepCount); |
| int count = attr.isValid() ? attr.toInt() : 1; |
| backend->stepInto(count); |
| response.setAsync(true); |
| } break; |
| |
| case QScriptDebuggerCommand::StepOver: { |
| QVariant attr = command.attribute(QScriptDebuggerCommand::StepCount); |
| int count = attr.isValid() ? attr.toInt() : 1; |
| backend->stepOver(count); |
| response.setAsync(true); |
| } break; |
| |
| case QScriptDebuggerCommand::StepOut: |
| backend->stepOut(); |
| response.setAsync(true); |
| break; |
| |
| case QScriptDebuggerCommand::RunToLocation: |
| backend->runToLocation(command.fileName(), command.lineNumber()); |
| response.setAsync(true); |
| break; |
| |
| case QScriptDebuggerCommand::RunToLocationByID: |
| backend->runToLocation(command.scriptId(), command.lineNumber()); |
| response.setAsync(true); |
| break; |
| |
| case QScriptDebuggerCommand::ForceReturn: { |
| int contextIndex = command.contextIndex(); |
| QScriptDebuggerValue value = command.scriptValue(); |
| QScriptEngine *engine = backend->engine(); |
| QScriptValue realValue = value.toScriptValue(engine); |
| backend->returnToCaller(contextIndex, realValue); |
| response.setAsync(true); |
| } break; |
| |
| case QScriptDebuggerCommand::Resume: |
| backend->resume(); |
| response.setAsync(true); |
| break; |
| |
| case QScriptDebuggerCommand::SetBreakpoint: { |
| QScriptBreakpointData data = command.breakpointData(); |
| if (!data.isValid()) |
| data = QScriptBreakpointData(command.fileName(), command.lineNumber()); |
| int id = backend->setBreakpoint(data); |
| response.setResult(id); |
| } break; |
| |
| case QScriptDebuggerCommand::DeleteBreakpoint: { |
| int id = command.breakpointId(); |
| if (!backend->deleteBreakpoint(id)) |
| response.setError(QScriptDebuggerResponse::InvalidBreakpointID); |
| } break; |
| |
| case QScriptDebuggerCommand::DeleteAllBreakpoints: |
| backend->deleteAllBreakpoints(); |
| break; |
| |
| case QScriptDebuggerCommand::GetBreakpoints: { |
| QScriptBreakpointMap bps = backend->breakpoints(); |
| if (!bps.isEmpty()) |
| response.setResult(bps); |
| } break; |
| |
| case QScriptDebuggerCommand::GetBreakpointData: { |
| int id = command.breakpointId(); |
| QScriptBreakpointData data = backend->breakpointData(id); |
| if (data.isValid()) |
| response.setResult(data); |
| else |
| response.setError(QScriptDebuggerResponse::InvalidBreakpointID); |
| } break; |
| |
| case QScriptDebuggerCommand::SetBreakpointData: { |
| int id = command.breakpointId(); |
| QScriptBreakpointData data = command.breakpointData(); |
| if (!backend->setBreakpointData(id, data)) |
| response.setError(QScriptDebuggerResponse::InvalidBreakpointID); |
| } break; |
| |
| case QScriptDebuggerCommand::GetScripts: { |
| QScriptScriptMap scripts = backend->scripts(); |
| if (!scripts.isEmpty()) |
| response.setResult(scripts); |
| } break; |
| |
| case QScriptDebuggerCommand::GetScriptData: { |
| qint64 id = command.scriptId(); |
| QScriptScriptData data = backend->scriptData(id); |
| if (data.isValid()) |
| response.setResult(data); |
| else |
| response.setError(QScriptDebuggerResponse::InvalidScriptID); |
| } break; |
| |
| case QScriptDebuggerCommand::ScriptsCheckpoint: |
| backend->scriptsCheckpoint(); |
| response.setResult(qVariantFromValue(backend->scriptsDelta())); |
| break; |
| |
| case QScriptDebuggerCommand::GetScriptsDelta: |
| response.setResult(qVariantFromValue(backend->scriptsDelta())); |
| break; |
| |
| case QScriptDebuggerCommand::ResolveScript: |
| response.setResult(backend->resolveScript(command.fileName())); |
| break; |
| |
| case QScriptDebuggerCommand::GetBacktrace: |
| response.setResult(backend->backtrace()); |
| break; |
| |
| case QScriptDebuggerCommand::GetContextCount: |
| response.setResult(backend->contextCount()); |
| break; |
| |
| case QScriptDebuggerCommand::GetContextState: { |
| QScriptContext *ctx = backend->context(command.contextIndex()); |
| if (ctx) |
| response.setResult(static_cast<int>(ctx->state())); |
| else |
| response.setError(QScriptDebuggerResponse::InvalidContextIndex); |
| } break; |
| |
| case QScriptDebuggerCommand::GetContextID: { |
| int idx = command.contextIndex(); |
| if ((idx >= 0) && (idx < backend->contextCount())) |
| response.setResult(backend->contextIds()[idx]); |
| else |
| response.setError(QScriptDebuggerResponse::InvalidContextIndex); |
| } break; |
| |
| case QScriptDebuggerCommand::GetContextInfo: { |
| QScriptContext *ctx = backend->context(command.contextIndex()); |
| if (ctx) |
| response.setResult(QScriptContextInfo(ctx)); |
| else |
| response.setError(QScriptDebuggerResponse::InvalidContextIndex); |
| } break; |
| |
| case QScriptDebuggerCommand::GetThisObject: { |
| QScriptContext *ctx = backend->context(command.contextIndex()); |
| if (ctx) |
| response.setResult(ctx->thisObject()); |
| else |
| response.setError(QScriptDebuggerResponse::InvalidContextIndex); |
| } break; |
| |
| case QScriptDebuggerCommand::GetActivationObject: { |
| QScriptContext *ctx = backend->context(command.contextIndex()); |
| if (ctx) |
| response.setResult(ctx->activationObject()); |
| else |
| response.setError(QScriptDebuggerResponse::InvalidContextIndex); |
| } break; |
| |
| case QScriptDebuggerCommand::GetScopeChain: { |
| QScriptContext *ctx = backend->context(command.contextIndex()); |
| if (ctx) { |
| QScriptDebuggerValueList dest; |
| QScriptValueList src = ctx->scopeChain(); |
| for (int i = 0; i < src.size(); ++i) |
| dest.append(src.at(i)); |
| response.setResult(dest); |
| } else { |
| response.setError(QScriptDebuggerResponse::InvalidContextIndex); |
| } |
| } break; |
| |
| case QScriptDebuggerCommand::ContextsCheckpoint: { |
| response.setResult(qVariantFromValue(backend->contextsCheckpoint())); |
| } break; |
| |
| case QScriptDebuggerCommand::GetPropertyExpressionValue: { |
| QScriptContext *ctx = backend->context(command.contextIndex()); |
| int lineNumber = command.lineNumber(); |
| QVariant attr = command.attribute(QScriptDebuggerCommand::UserAttribute); |
| QStringList path = attr.toStringList(); |
| if (!ctx || path.isEmpty()) |
| break; |
| QScriptContextInfo ctxInfo(ctx); |
| if (ctx->callee().isValid() |
| && ((lineNumber < ctxInfo.functionStartLineNumber()) |
| || (lineNumber > ctxInfo.functionEndLineNumber()))) { |
| break; |
| } |
| QScriptValueList objects; |
| int pathIndex = 0; |
| if (path.at(0) == QLatin1String("this")) { |
| objects.append(ctx->thisObject()); |
| ++pathIndex; |
| } else { |
| objects << ctx->scopeChain(); |
| } |
| for (int i = 0; i < objects.size(); ++i) { |
| QScriptValue val = objects.at(i); |
| for (int j = pathIndex; val.isValid() && (j < path.size()); ++j) { |
| val = val.property(path.at(j)); |
| } |
| if (val.isValid()) { |
| bool hadException = (ctx->state() == QScriptContext::ExceptionState); |
| QString str = val.toString(); |
| if (!hadException && backend->engine()->hasUncaughtException()) |
| backend->engine()->clearExceptions(); |
| response.setResult(str); |
| break; |
| } |
| } |
| } break; |
| |
| case QScriptDebuggerCommand::GetCompletions: { |
| QScriptContext *ctx = backend->context(command.contextIndex()); |
| QVariant attr = command.attribute(QScriptDebuggerCommand::UserAttribute); |
| QStringList path = attr.toStringList(); |
| if (!ctx || path.isEmpty()) |
| break; |
| QScriptValueList objects; |
| QString prefix = path.last(); |
| QSet<QString> matches; |
| if (path.size() > 1) { |
| const QString &topLevelIdent = path.at(0); |
| QScriptValue obj; |
| if (topLevelIdent == QLatin1String("this")) { |
| obj = ctx->thisObject(); |
| } else { |
| QScriptValueList scopeChain; |
| scopeChain = ctx->scopeChain(); |
| for (int i = 0; i < scopeChain.size(); ++i) { |
| QScriptValue oo = scopeChain.at(i).property(topLevelIdent); |
| if (oo.isObject()) { |
| obj = oo; |
| break; |
| } |
| } |
| } |
| for (int i = 1; obj.isObject() && (i < path.size()-1); ++i) |
| obj = obj.property(path.at(i)); |
| if (obj.isValid()) |
| objects.append(obj); |
| } else { |
| objects << ctx->scopeChain(); |
| QStringList keywords; |
| keywords.append(QString::fromLatin1("this")); |
| keywords.append(QString::fromLatin1("true")); |
| keywords.append(QString::fromLatin1("false")); |
| keywords.append(QString::fromLatin1("null")); |
| for (int i = 0; i < keywords.size(); ++i) { |
| const QString &kwd = keywords.at(i); |
| if (isPrefixOf(prefix, kwd)) |
| matches.insert(kwd); |
| } |
| } |
| |
| for (int i = 0; i < objects.size(); ++i) { |
| QScriptValue obj = objects.at(i); |
| while (obj.isObject()) { |
| QScriptValueIterator it(obj); |
| while (it.hasNext()) { |
| it.next(); |
| QString propertyName = it.name(); |
| if (isPrefixOf(prefix, propertyName)) |
| matches.insert(propertyName); |
| } |
| obj = obj.prototype(); |
| } |
| } |
| QStringList matchesList = matches.toList(); |
| qStableSort(matchesList); |
| response.setResult(matchesList); |
| } break; |
| |
| case QScriptDebuggerCommand::NewScriptObjectSnapshot: { |
| int id = backend->newScriptObjectSnapshot(); |
| response.setResult(id); |
| } break; |
| |
| case QScriptDebuggerCommand::ScriptObjectSnapshotCapture: { |
| int id = command.snapshotId(); |
| QScriptObjectSnapshot *snap = backend->scriptObjectSnapshot(id); |
| Q_ASSERT(snap != 0); |
| QScriptDebuggerValue object = command.scriptValue(); |
| Q_ASSERT(object.type() == QScriptDebuggerValue::ObjectValue); |
| QScriptEngine *engine = backend->engine(); |
| QScriptValue realObject = object.toScriptValue(engine); |
| Q_ASSERT(realObject.isObject()); |
| QScriptObjectSnapshot::Delta delta = snap->capture(realObject); |
| QScriptDebuggerObjectSnapshotDelta result; |
| result.removedProperties = delta.removedProperties; |
| bool didIgnoreExceptions = backend->ignoreExceptions(); |
| backend->setIgnoreExceptions(true); |
| for (int i = 0; i < delta.changedProperties.size(); ++i) { |
| const QScriptValueProperty &src = delta.changedProperties.at(i); |
| bool hadException = engine->hasUncaughtException(); |
| QString str = src.value().toString(); |
| if (!hadException && engine->hasUncaughtException()) |
| engine->clearExceptions(); |
| QScriptDebuggerValueProperty dest(src.name(), src.value(), str, src.flags()); |
| result.changedProperties.append(dest); |
| } |
| for (int j = 0; j < delta.addedProperties.size(); ++j) { |
| const QScriptValueProperty &src = delta.addedProperties.at(j); |
| bool hadException = engine->hasUncaughtException(); |
| QString str = src.value().toString(); |
| if (!hadException && engine->hasUncaughtException()) |
| engine->clearExceptions(); |
| QScriptDebuggerValueProperty dest(src.name(), src.value(), str, src.flags()); |
| result.addedProperties.append(dest); |
| } |
| backend->setIgnoreExceptions(didIgnoreExceptions); |
| response.setResult(qVariantFromValue(result)); |
| } break; |
| |
| case QScriptDebuggerCommand::DeleteScriptObjectSnapshot: { |
| int id = command.snapshotId(); |
| backend->deleteScriptObjectSnapshot(id); |
| } break; |
| |
| case QScriptDebuggerCommand::NewScriptValueIterator: { |
| QScriptDebuggerValue object = command.scriptValue(); |
| Q_ASSERT(object.type() == QScriptDebuggerValue::ObjectValue); |
| QScriptEngine *engine = backend->engine(); |
| QScriptValue realObject = object.toScriptValue(engine); |
| Q_ASSERT(realObject.isObject()); |
| int id = backend->newScriptValueIterator(realObject); |
| response.setResult(id); |
| } break; |
| |
| case QScriptDebuggerCommand::GetPropertiesByIterator: { |
| int id = command.iteratorId(); |
| int count = 1000; |
| QScriptValueIterator *it = backend->scriptValueIterator(id); |
| Q_ASSERT(it != 0); |
| QScriptDebuggerValuePropertyList props; |
| for (int i = 0; (i < count) && it->hasNext(); ++i) { |
| it->next(); |
| QString name = it->name(); |
| QScriptValue value = it->value(); |
| QString valueAsString = value.toString(); |
| QScriptValue::PropertyFlags flags = it->flags(); |
| QScriptDebuggerValueProperty prp(name, value, valueAsString, flags); |
| props.append(prp); |
| } |
| response.setResult(props); |
| } break; |
| |
| case QScriptDebuggerCommand::DeleteScriptValueIterator: { |
| int id = command.iteratorId(); |
| backend->deleteScriptValueIterator(id); |
| } break; |
| |
| case QScriptDebuggerCommand::Evaluate: { |
| int contextIndex = command.contextIndex(); |
| QString program = command.program(); |
| QString fileName = command.fileName(); |
| int lineNumber = command.lineNumber(); |
| backend->evaluate(contextIndex, program, fileName, lineNumber); |
| response.setAsync(true); |
| } break; |
| |
| case QScriptDebuggerCommand::ScriptValueToString: { |
| QScriptDebuggerValue value = command.scriptValue(); |
| QScriptEngine *engine = backend->engine(); |
| QScriptValue realValue = value.toScriptValue(engine); |
| response.setResult(realValue.toString()); |
| } break; |
| |
| case QScriptDebuggerCommand::SetScriptValueProperty: { |
| QScriptDebuggerValue object = command.scriptValue(); |
| QScriptEngine *engine = backend->engine(); |
| QScriptValue realObject = object.toScriptValue(engine); |
| QScriptDebuggerValue value = command.subordinateScriptValue(); |
| QScriptValue realValue = value.toScriptValue(engine); |
| QString name = command.name(); |
| realObject.setProperty(name, realValue); |
| } break; |
| |
| case QScriptDebuggerCommand::ClearExceptions: |
| backend->engine()->clearExceptions(); |
| break; |
| |
| case QScriptDebuggerCommand::UserCommand: |
| case QScriptDebuggerCommand::MaxUserCommand: |
| break; |
| } |
| return response; |
| } |
| |
| QT_END_NAMESPACE |