Qt Reference Documentation

# scriptdebugger.cpp Example File

script/qsdbg/scriptdebugger.cpp

     /****************************************************************************
     **
     ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
     ** All rights reserved.
     ** Contact: Nokia Corporation (qt-info@nokia.com)
     **
     ** This file is part of the examples of the Qt Toolkit.
     **
     ** $QT_BEGIN_LICENSE:BSD$
     ** You may use this file under the terms of the BSD license as follows:
     **
     ** "Redistribution and use in source and binary forms, with or without
     ** modification, are permitted provided that the following conditions are
     ** met:
     **   * Redistributions of source code must retain the above copyright
     **     notice, this list of conditions and the following disclaimer.
     **   * Redistributions in binary form must reproduce the above copyright
     **     notice, this list of conditions and the following disclaimer in
     **     the documentation and/or other materials provided with the
     **     distribution.
     **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
     **     the names of its contributors may be used to endorse or promote
     **     products derived from this software without specific prior written
     **     permission.
     **
     ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
     ** $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;
     }