diff options
Diffstat (limited to 'examples/script/qsdbg/scriptdebugger.cpp')
-rw-r--r-- | examples/script/qsdbg/scriptdebugger.cpp | 737 |
1 files changed, 737 insertions, 0 deletions
diff --git a/examples/script/qsdbg/scriptdebugger.cpp b/examples/script/qsdbg/scriptdebugger.cpp new file mode 100644 index 0000000..e3639b9 --- /dev/null +++ b/examples/script/qsdbg/scriptdebugger.cpp @@ -0,0 +1,737 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "scriptdebugger.h" +#include "scriptbreakpointmanager.h" + +#include <QtScript/QScriptEngine> +#include <QtScript/QScriptEngineAgent> +#include <QtScript/QScriptContextInfo> +#include <QtScript/QScriptValueIterator> +#include <QtCore/QTextStream> +#include <QtCore/QStack> + +static QString safeValueToString(const QScriptValue &value) +{ + if (value.isObject()) + return QLatin1String("[object Object]"); + else + return value.toString(); +} + +class ScriptInfo; +class ScriptBreakpointManager; + +class ScriptDebuggerPrivate + : public QScriptEngineAgent +{ + Q_DECLARE_PUBLIC(ScriptDebugger) +public: + enum Mode { + Run, + StepInto, + StepOver + }; + + ScriptDebuggerPrivate(QScriptEngine *engine); + ~ScriptDebuggerPrivate(); + + // QScriptEngineAgent interface + void scriptLoad(qint64 id, const QString &program, + const QString &fileName, int lineNumber); + void scriptUnload(qint64 id); + + void positionChange(qint64 scriptId, + int lineNumber, int columnNumber); + + void functionEntry(qint64 scriptId); + void functionExit(qint64 scriptId, + const QScriptValue &returnValue); + + void exceptionThrow(qint64 scriptId, + const QScriptValue &exception, bool hasHandler); + + + void interactive(); + bool executeCommand(const QString &command, const QStringList &args); + + void setMode(Mode mode); + Mode mode() const; + + int frameCount() const; + void setCurrentFrameIndex(int index); + int currentFrameIndex() const; + + QScriptContext *frameContext(int index) const; + QScriptContext *currentFrameContext() const; + + ScriptInfo *scriptInfo(QScriptContext *context) const; + + int listLineNumber() const; + void setListLineNumber(int lineNumber); + + QString readLine(); + void output(const QString &text); + void message(const QString &text); + void errorMessage(const QString &text); + + // attributes + QTextStream *m_defaultInputStream; + QTextStream *m_defaultOutputStream; + QTextStream *m_defaultErrorStream; + QTextStream *m_inputStream; + QTextStream *m_outputStream; + QTextStream *m_errorStream; + + ScriptBreakpointManager *m_bpManager; + Mode m_mode; + QMap<qint64, ScriptInfo*> m_scripts; + QMap<QScriptContext*, QStack<qint64> > m_contextProgramIds; + + QString m_lastInteractiveCommand; + QString m_commandPrefix; + int m_stepDepth; + int m_currentFrameIndex; + int m_listLineNumber; + + ScriptDebugger *q_ptr; +}; + +class ScriptInfo +{ +public: + ScriptInfo(const QString &code, const QString &fileName, int lineNumber) + : m_code(code), m_fileName(fileName), m_lineNumber(lineNumber) + { } + + inline QString code() const + { return m_code; } + inline QString fileName() const + { return m_fileName; } + inline int lineNumber() const + { return m_lineNumber; } + + QString lineText(int lineNumber); + QMap<int, int> m_lineOffsets; + +private: + int lineOffset(int lineNumber); + + QString m_code; + QString m_fileName; + int m_lineNumber; +}; + +int ScriptInfo::lineOffset(int lineNumber) +{ + QMap<int, int>::const_iterator it = m_lineOffsets.constFind(lineNumber); + if (it != m_lineOffsets.constEnd()) + return it.value(); + + int offset; + it = m_lineOffsets.constFind(lineNumber - 1); + if (it != m_lineOffsets.constEnd()) { + offset = it.value(); + offset = m_code.indexOf(QLatin1Char('\n'), offset); + if (offset != -1) + ++offset; + m_lineOffsets.insert(lineNumber, offset); + } else { + int index; + it = m_lineOffsets.lowerBound(lineNumber); + --it; + if (it != m_lineOffsets.constBegin()) { + index = it.key(); + offset = it.value(); + } else { + index = m_lineNumber; + offset = 0; + } + int j = index; + for ( ; j < lineNumber; ++j) { + m_lineOffsets.insert(j, offset); + offset = m_code.indexOf(QLatin1Char('\n'), offset); + if (offset == -1) + break; + ++offset; + } + m_lineOffsets.insert(j, offset); + } + return offset; +} + +QString ScriptInfo::lineText(int lineNumber) +{ + int startOffset = lineOffset(lineNumber); + if (startOffset == -1) + return QString(); + int endOffset = lineOffset(lineNumber + 1); + if (endOffset == -1) + return m_code.mid(startOffset); + else + return m_code.mid(startOffset, endOffset - startOffset - 1); +} + + + +ScriptDebuggerPrivate::ScriptDebuggerPrivate(QScriptEngine *engine) + : QScriptEngineAgent(engine), m_mode(Run) +{ + m_commandPrefix = QLatin1String("."); + m_bpManager = new ScriptBreakpointManager; + m_defaultInputStream = new QTextStream(stdin); + m_defaultOutputStream = new QTextStream(stdout); + m_defaultErrorStream = new QTextStream(stderr); + m_inputStream = m_defaultInputStream; + m_outputStream = m_defaultOutputStream; + m_errorStream = m_defaultErrorStream; +} + +ScriptDebuggerPrivate::~ScriptDebuggerPrivate() +{ + delete m_defaultInputStream; + delete m_defaultOutputStream; + delete m_defaultErrorStream; + delete m_bpManager; + qDeleteAll(m_scripts); +} + +QString ScriptDebuggerPrivate::readLine() +{ + return m_inputStream->readLine(); +} + +void ScriptDebuggerPrivate::output(const QString &text) +{ + *m_outputStream << text; +} + +void ScriptDebuggerPrivate::message(const QString &text) +{ + *m_outputStream << text << endl; + m_outputStream->flush(); +} + +void ScriptDebuggerPrivate::errorMessage(const QString &text) +{ + *m_errorStream << text << endl; + m_errorStream->flush(); +} + +void ScriptDebuggerPrivate::setMode(Mode mode) +{ + m_mode = mode; +} + +ScriptDebuggerPrivate::Mode ScriptDebuggerPrivate::mode() const +{ + return m_mode; +} + +QScriptContext *ScriptDebuggerPrivate::frameContext(int index) const +{ + QScriptContext *ctx = engine()->currentContext(); + for (int i = 0; i < index; ++i) { + ctx = ctx->parentContext(); + if (!ctx) + break; + } + return ctx; +} + +int ScriptDebuggerPrivate::currentFrameIndex() const +{ + return m_currentFrameIndex; +} + +void ScriptDebuggerPrivate::setCurrentFrameIndex(int index) +{ + m_currentFrameIndex = index; + m_listLineNumber = -1; +} + +int ScriptDebuggerPrivate::listLineNumber() const +{ + return m_listLineNumber; +} + +void ScriptDebuggerPrivate::setListLineNumber(int lineNumber) +{ + m_listLineNumber = lineNumber; +} + +QScriptContext *ScriptDebuggerPrivate::currentFrameContext() const +{ + return frameContext(currentFrameIndex()); +} + +int ScriptDebuggerPrivate::frameCount() const +{ + int count = 0; + QScriptContext *ctx = engine()->currentContext(); + while (ctx) { + ++count; + ctx = ctx->parentContext(); + } + return count; +} + +ScriptInfo *ScriptDebuggerPrivate::scriptInfo(QScriptContext *context) const +{ + QStack<qint64> pids = m_contextProgramIds.value(context); + if (pids.isEmpty()) + return 0; + return m_scripts.value(pids.top()); +} + +void ScriptDebuggerPrivate::interactive() +{ + setCurrentFrameIndex(0); + + QString qsdbgPrompt = QString::fromLatin1("(qsdbg) "); + QString dotPrompt = QString::fromLatin1(".... "); + QString prompt = qsdbgPrompt; + + QString code; + + forever { + + *m_outputStream << prompt; + m_outputStream->flush(); + + QString line = readLine(); + + if (code.isEmpty() && (line.isEmpty() || line.startsWith(m_commandPrefix))) { + if (line.isEmpty()) + line = m_lastInteractiveCommand; + else + m_lastInteractiveCommand = line; + + QStringList parts = line.split(QLatin1Char(' '), QString::SkipEmptyParts); + if (!parts.isEmpty()) { + QString command = parts.takeFirst().mid(1); + if (executeCommand(command, parts)) + break; + } + + } else { + if (line.isEmpty()) + continue; + + code += line; + code += QLatin1Char('\n'); + + if (line.trimmed().isEmpty()) { + continue; + + } else if (! engine()->canEvaluate(code)) { + prompt = dotPrompt; + + } else { + setMode(Run); + QScriptValue result = engine()->evaluate(code, QLatin1String("typein")); + + code.clear(); + prompt = qsdbgPrompt; + + if (! result.isUndefined()) { + errorMessage(result.toString()); + engine()->clearExceptions(); + } + } + } + } +} + +bool ScriptDebuggerPrivate::executeCommand(const QString &command, const QStringList &args) +{ + if (command == QLatin1String("c") + || command == QLatin1String("continue")) { + setMode(Run); + return true; + } else if (command == QLatin1String("s") + || command == QLatin1String("step")) { + setMode(StepInto); + return true; + } else if (command == QLatin1String("n") + || command == QLatin1String("next")) { + setMode(StepOver); + m_stepDepth = 0; + return true; + } else if (command == QLatin1String("f") + || command == QLatin1String("frame")) { + bool ok = false; + int index = args.value(0).toInt(&ok); + if (ok) { + if (index < 0 || index >= frameCount()) { + errorMessage("No such frame."); + } else { + setCurrentFrameIndex(index); + QScriptContext *ctx = currentFrameContext(); + message(QString::fromLatin1("#%0 %1").arg(index).arg(ctx->toString())); + } + } + } else if (command == QLatin1String("bt") + || command == QLatin1String("backtrace")) { + QScriptContext *ctx = engine()->currentContext(); + int index = -1; + while (ctx) { + ++index; + QString line = ctx->toString(); + message(QString::fromLatin1("#%0 %1").arg(index).arg(line)); + ctx = ctx->parentContext(); + } + } else if (command == QLatin1String("up")) { + int index = currentFrameIndex() + 1; + if (index == frameCount()) { + errorMessage(QString::fromLatin1("Initial frame selected; you cannot go up.")); + } else { + setCurrentFrameIndex(index); + QScriptContext *ctx = currentFrameContext(); + message(QString::fromLatin1("#%0 %1").arg(index).arg(ctx->toString())); + } + } else if (command == QLatin1String("down")) { + int index = currentFrameIndex() - 1; + if (index < 0) { + errorMessage(QString::fromLatin1("Bottom (innermost) frame selected; you cannot go down.")); + } else { + setCurrentFrameIndex(index); + QScriptContext *ctx = currentFrameContext(); + message(QString::fromLatin1("#%0 %1").arg(index).arg(ctx->toString())); + } + } else if (command == QLatin1String("b") + || command == QLatin1String("break")) { + QString str = args.value(0); + int colonIndex = str.indexOf(QLatin1Char(':')); + if (colonIndex != -1) { + // filename:line form + QString fileName = str.left(colonIndex); + int lineNumber = str.mid(colonIndex+1).toInt(); + int id = m_bpManager->setBreakpoint(fileName, lineNumber); + message(QString::fromLatin1("Breakpoint %0 at %1, line %2.").arg(id+1).arg(fileName).arg(lineNumber)); + } else { + // function + QScriptValue fun = engine()->globalObject().property(str); + if (fun.isFunction()) { + int id = m_bpManager->setBreakpoint(fun); + message(QString::fromLatin1("Breakpoint %0 at %1().").arg(id+1).arg(str)); + } + } + } else if (command == QLatin1String("d") + || command == QLatin1String("delete")) { + int id = args.value(0).toInt() - 1; + m_bpManager->removeBreakpoint(id); + } else if (command == QLatin1String("disable")) { + int id = args.value(0).toInt() - 1; + m_bpManager->setBreakpointEnabled(id, false); + } else if (command == QLatin1String("enable")) { + int id = args.value(0).toInt() - 1; + m_bpManager->setBreakpointEnabled(id, true); + } else if (command == QLatin1String("list")) { + QScriptContext *ctx = currentFrameContext(); + ScriptInfo *progInfo = scriptInfo(ctx); + if (!progInfo) { + errorMessage("No source text available for this frame."); + } else { + QScriptContextInfo ctxInfo(ctx); + bool ok; + int line = args.value(0).toInt(&ok); + if (ok) { + line = qMax(1, line - 5); + } else { + line = listLineNumber(); + if (line == -1) + line = qMax(progInfo->lineNumber(), ctxInfo.lineNumber() - 5); + } + for (int i = line; i < line + 10; ++i) { + message(QString::fromLatin1("%0\t%1").arg(i).arg(progInfo->lineText(i))); + } + setListLineNumber(line + 10); + } + } else if (command == QLatin1String("info")) { + if (args.size() < 1) { + } else { + QString what = args.value(0); + if (what == QLatin1String("locals")) { + QScriptValueIterator it(currentFrameContext()->activationObject()); + while (it.hasNext()) { + it.next(); + QString line; + line.append(it.name()); + line.append(QLatin1String(" = ")); + line.append(safeValueToString(it.value())); + message(line); + } + } + } + } else if (command == QLatin1String("help")) { + message("continue - continue execution\n" + "step - step into statement\n" + "next - step over statement\n" + "list - show where you are\n" + "\n" + "break - set breakpoint\n" + "delete - remove breakpoint\n" + "disable - disable breakpoint\n" + "enable - enable breakpoint\n" + "\n" + "backtrace - show backtrace\n" + "up - one frame up\n" + "down - one frame down\n" + "frame - set frame\n" + "\n" + "info locals - show local variables"); + } else { + errorMessage(QString::fromLatin1("Undefined command \"%0\". Try \"help\".") + .arg(command)); + } + + return false; +} + + +// QScriptEngineAgent interface + +void ScriptDebuggerPrivate::scriptLoad(qint64 id, const QString &program, + const QString &fileName, int lineNumber) +{ + ScriptInfo *info = new ScriptInfo(program, fileName, lineNumber); + m_scripts.insert(id, info); +} + +void ScriptDebuggerPrivate::scriptUnload(qint64 id) +{ + ScriptInfo *info = m_scripts.take(id); + delete info; +} + +void ScriptDebuggerPrivate::functionEntry(qint64 scriptId) +{ + if (scriptId != -1) { + QScriptContext *ctx = engine()->currentContext(); + QStack<qint64> ids = m_contextProgramIds.value(ctx); + ids.push(scriptId); + m_contextProgramIds.insert(ctx, ids); + } + + if (mode() == StepOver) + ++m_stepDepth; +} + +void ScriptDebuggerPrivate::functionExit(qint64 scriptId, + const QScriptValue &/*returnValue*/) +{ + if (scriptId != -1) { + QScriptContext *ctx = engine()->currentContext(); + QStack<qint64> ids = m_contextProgramIds.value(ctx); + Q_ASSERT(!ids.isEmpty()); + Q_ASSERT(ids.top() == scriptId); + ids.pop(); + m_contextProgramIds.insert(ctx, ids); + } + + if (mode() == StepOver) + --m_stepDepth; +} + +void ScriptDebuggerPrivate::positionChange(qint64 scriptId, + int lineNumber, int /*columnNumber*/) +{ + ScriptInfo *info = 0; + bool enterInteractiveMode = false; + + if (m_bpManager->hasBreakpoints()) { + // check if we hit a breakpoint + info = m_scripts.value(scriptId); + QScriptContext *ctx = engine()->currentContext(); + QScriptContextInfo ctxInfo(ctx); + QScriptValue callee = ctx->callee(); + + // try fileName:lineNumber + int bpid = m_bpManager->findBreakpoint(info->fileName(), lineNumber); + if ((bpid != -1) && m_bpManager->isBreakpointEnabled(bpid)) { + message(QString::fromLatin1("Breakpoint %0 at %1:%2") + .arg(bpid + 1).arg(info->fileName()).arg(lineNumber)); + if (m_bpManager->isBreakpointSingleShot(bpid)) + m_bpManager->removeBreakpoint(bpid); + } + if (bpid == -1) { + // try function + bpid = m_bpManager->findBreakpoint(callee); + if ((bpid != -1) && m_bpManager->isBreakpointEnabled(bpid)) { + message(QString::fromLatin1("Breakpoint %0, %1()") + .arg(bpid + 1).arg(ctxInfo.functionName())); + if (m_bpManager->isBreakpointSingleShot(bpid)) + m_bpManager->removeBreakpoint(bpid); + } + } + if ((bpid == -1) && !ctxInfo.functionName().isEmpty()) { + // try functionName:fileName + bpid = m_bpManager->findBreakpoint(ctxInfo.functionName(), ctxInfo.fileName()); + if ((bpid != -1) && m_bpManager->isBreakpointEnabled(bpid)) { + message(QString::fromLatin1("Breakpoint %0, %1():%2").arg(bpid + 1) + .arg(ctxInfo.functionName()).arg(ctxInfo.fileName())); + if (m_bpManager->isBreakpointSingleShot(bpid)) + m_bpManager->removeBreakpoint(bpid); + } + } + + enterInteractiveMode = (bpid != -1); + } + + switch (mode()) { + case Run: + break; + + case StepInto: + enterInteractiveMode = true; + break; + + case StepOver: + enterInteractiveMode = enterInteractiveMode || (m_stepDepth <= 0); + break; + } + + if (enterInteractiveMode) { + if (!info) + info = m_scripts.value(scriptId); + Q_ASSERT(info); + message(QString::fromLatin1("%0\t%1").arg(lineNumber).arg(info->lineText(lineNumber))); + interactive(); + } +} + +void ScriptDebuggerPrivate::exceptionThrow(qint64 /*scriptId*/, + const QScriptValue &exception, + bool hasHandler) +{ + if (!hasHandler) { + errorMessage(QString::fromLatin1("uncaught exception: %0").arg(exception.toString())); + QScriptContext *ctx = engine()->currentContext(); + int lineNumber = QScriptContextInfo(ctx).lineNumber(); + ScriptInfo *info = scriptInfo(ctx); + QString lineText = info ? info->lineText(lineNumber) : QString("(no source text available)"); + message(QString::fromLatin1("%0\t%1").arg(lineNumber).arg(lineText)); + interactive(); + } +} + + + +ScriptDebugger::ScriptDebugger(QScriptEngine *engine) + : d_ptr(new ScriptDebuggerPrivate(engine)) +{ + d_ptr->q_ptr = this; + engine->setAgent(d_ptr); +} + +ScriptDebugger::ScriptDebugger(QScriptEngine *engine, ScriptDebuggerPrivate &dd) + : d_ptr(&dd) +{ + d_ptr->q_ptr = this; + engine->setAgent(d_ptr); +} + +ScriptDebugger::~ScriptDebugger() +{ + delete d_ptr; + d_ptr = 0; +} + +void ScriptDebugger::breakAtNextStatement() +{ + Q_D(ScriptDebugger); + d->setMode(ScriptDebuggerPrivate::StepInto); +} + +void ScriptDebugger::setBreakpoint(const QString &fileName, int lineNumber) +{ + Q_D(ScriptDebugger); + d->m_bpManager->setBreakpoint(fileName, lineNumber); +} + +void ScriptDebugger::setBreakpoint(const QString &functionName, const QString &fileName) +{ + Q_D(ScriptDebugger); + d->m_bpManager->setBreakpoint(functionName, fileName); +} + +void ScriptDebugger::setBreakpoint(const QScriptValue &function) +{ + Q_D(ScriptDebugger); + d->m_bpManager->setBreakpoint(function); +} + +QTextStream *ScriptDebugger::inputStream() const +{ + Q_D(const ScriptDebugger); + return d->m_inputStream; +} + +void ScriptDebugger::setInputStream(QTextStream *inputStream) +{ + Q_D(ScriptDebugger); + d->m_inputStream = inputStream; +} + +QTextStream *ScriptDebugger::outputStream() const +{ + Q_D(const ScriptDebugger); + return d->m_outputStream; +} + +void ScriptDebugger::setOutputStream(QTextStream *outputStream) +{ + Q_D(ScriptDebugger); + d->m_outputStream = outputStream; +} + +QTextStream *ScriptDebugger::errorStream() const +{ + Q_D(const ScriptDebugger); + return d->m_errorStream; +} + +void ScriptDebugger::setErrorStream(QTextStream *errorStream) +{ + Q_D(ScriptDebugger); + d->m_errorStream = errorStream; +} |