From 63ddfee7550b4d12ddb56e4575d8bb27c664c41e Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Wed, 11 Sep 2013 13:46:27 +0200 Subject: [PATCH] V4 debugger: retrieve formals and locals. Change-Id: I47507a4d7d1b429b9c43ed3a7822079efe577327 Reviewed-by: Simon Hausmann --- src/qml/compiler/qqmlcodegenerator_p.h | 5 +- src/qml/compiler/qv4jsir_p.h | 6 +- src/qml/compiler/qv4ssa.cpp | 2 +- src/qml/jsruntime/qv4debugging.cpp | 281 +++++++++++++++++-------- src/qml/jsruntime/qv4debugging_p.h | 35 ++- src/qml/jsruntime/qv4functionobject.cpp | 2 +- src/qml/jsruntime/qv4script.cpp | 4 +- src/qml/qml/qqmlcompiler.cpp | 2 +- src/qml/qml/qqmltypeloader.cpp | 2 +- tests/auto/qml/qv4debugger/tst_qv4debugger.cpp | 167 ++++++++++++++- 10 files changed, 404 insertions(+), 102 deletions(-) diff --git a/src/qml/compiler/qqmlcodegenerator_p.h b/src/qml/compiler/qqmlcodegenerator_p.h index 8de08a8..1830b62 100644 --- a/src/qml/compiler/qqmlcodegenerator_p.h +++ b/src/qml/compiler/qqmlcodegenerator_p.h @@ -166,8 +166,9 @@ struct Pragma struct ParsedQML { - ParsedQML() - : jsGenerator(&jsModule, sizeof(QV4::CompiledData::QmlUnit)) + ParsedQML(bool debugMode) + : jsModule(debugMode) + , jsGenerator(&jsModule, sizeof(QV4::CompiledData::QmlUnit)) {} QString code; QQmlJS::Engine jsParserEngine; diff --git a/src/qml/compiler/qv4jsir_p.h b/src/qml/compiler/qv4jsir_p.h index 7b0ee52..8d090ca 100644 --- a/src/qml/compiler/qv4jsir_p.h +++ b/src/qml/compiler/qv4jsir_p.h @@ -683,12 +683,14 @@ struct Q_QML_EXPORT Module { Function *rootFunction; QString fileName; bool isQmlModule; // implies rootFunction is always 0 + bool debugMode; Function *newFunction(const QString &name, Function *outer); - Module() + Module(bool debugMode) : rootFunction(0) , isQmlModule(false) + , debugMode(debugMode) {} ~Module(); @@ -764,7 +766,7 @@ struct Function { int indexOfArgument(const QStringRef &string) const; bool variablesCanEscape() const - { return hasDirectEval || !nestedFunctions.isEmpty(); } + { return hasDirectEval || !nestedFunctions.isEmpty() || module->debugMode; } }; struct BasicBlock { diff --git a/src/qml/compiler/qv4ssa.cpp b/src/qml/compiler/qv4ssa.cpp index c9ff6ad..d1ebbcc 100644 --- a/src/qml/compiler/qv4ssa.cpp +++ b/src/qml/compiler/qv4ssa.cpp @@ -2858,7 +2858,7 @@ void Optimizer::run() static bool doSSA = qgetenv("QV4_NO_SSA").isEmpty(); static bool doOpt = qgetenv("QV4_NO_OPT").isEmpty(); - if (!function->hasTry && !function->hasWith && doSSA) { + if (!function->hasTry && !function->hasWith && !function->module->debugMode && doSSA) { // qout << "SSA for " << *function->name << endl; // qout << "Starting edge splitting..." << endl; splitCriticalEdges(function); diff --git a/src/qml/jsruntime/qv4debugging.cpp b/src/qml/jsruntime/qv4debugging.cpp index 41ed34e..a29aba8 100644 --- a/src/qml/jsruntime/qv4debugging.cpp +++ b/src/qml/jsruntime/qv4debugging.cpp @@ -44,6 +44,7 @@ #include "qv4functionobject_p.h" #include "qv4function_p.h" #include "qv4instr_moth_p.h" +#include "qv4runtime_p.h" #include #include @@ -52,7 +53,7 @@ using namespace QV4; using namespace QV4::Debugging; Debugger::Debugger(QV4::ExecutionEngine *engine) - : _engine(engine) + : m_engine(engine) , m_agent(0) , m_state(Running) , m_pauseRequested(false) @@ -122,7 +123,7 @@ Debugger::ExecutionState Debugger::currentExecutionState(const uchar *code) cons // ### Locking ExecutionState state; - QV4::ExecutionContext *context = _engine->current; + QV4::ExecutionContext *context = m_engine->current; QV4::Function *function = 0; if (CallContext *callCtx = context->asCallContext()) function = callCtx->function->function; @@ -145,6 +146,187 @@ void Debugger::setPendingBreakpoints(Function *function) m_pendingBreakPointsToAddToFutureCode.applyToFunction(function, /*removeBreakPoints*/ false); } +QVector Debugger::stackTrace(int frameLimit) const +{ + return m_engine->stackTrace(frameLimit); +} + +QList Debugger::retrieveFromValue(const ObjectRef o, const QStringList &path) const +{ + QList props; + if (!o) + return props; + + Scope scope(m_engine); + ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly); + ScopedValue name(scope); + ScopedValue val(scope); + while (true) { + Value v; + name = it.nextPropertyNameAsString(&v); + if (name->isNull()) + break; + QString key = name->toQStringNoThrow(); + if (path.isEmpty()) { + val = v; + QVariant varValue; + VarInfo::Type type; + convert(val, &varValue, &type); + props.append(VarInfo(key, varValue, type)); + } else if (path.first() == key) { + QStringList pathTail = path; + pathTail.pop_front(); + return retrieveFromValue(ScopedObject(scope, v), pathTail); + } + } + + return props; +} + +void Debugger::convert(ValueRef v, QVariant *varValue, VarInfo::Type *type) const +{ + Q_ASSERT(varValue); + Q_ASSERT(type); + + switch (v->type()) { + case Value::Empty_Type: + Q_ASSERT(!"empty Value encountered"); + break; + case Value::Undefined_Type: + *type = VarInfo::Undefined; + varValue->setValue(0); + break; + case Value::Null_Type: + *type = VarInfo::Null; + varValue->setValue(0); + break; + case Value::Boolean_Type: + *type = VarInfo::Bool; + varValue->setValue(v->booleanValue()); + break; + case Value::Managed_Type: + if (v->isString()) { + *type = VarInfo::String; + varValue->setValue(v->stringValue()->toQString()); + } else { + *type = VarInfo::Object; + ExecutionContext *ctx = v->objectValue()->internalClass->engine->current; + Scope scope(ctx); + ScopedValue prim(scope, __qmljs_to_primitive(v, STRING_HINT)); + varValue->setValue(prim->toQString()); + } + break; + case Value::Integer_Type: + *type = VarInfo::Number; + varValue->setValue((double)v->int_32); + break; + default: // double + *type = VarInfo::Number; + varValue->setValue(v->doubleValue()); + break; + } +} + +static CallContext *findContext(ExecutionContext *ctxt, int frame) +{ + while (ctxt) { + if (CallContext *cCtxt = ctxt->asCallContext()) { + if (frame < 1) + return cCtxt; + --frame; + } + ctxt = ctxt->parent; + } + + return 0; +} + +/// Retrieves all arguments from a context, or all properties in an object passed in an argument. +/// +/// \arg frame specifies the frame number: 0 is top of stack, 1 is the parent of the current frame, etc. +/// \arg path when empty, retrieve all arguments in the specified frame. When not empty, find the +/// argument with the same name as the first element in the path (in the specified frame, of +/// course), and then use the rest of the path to walk nested objects. When the path is empty, +/// retrieve all properties in that object. If an intermediate non-object is specified by the +/// path, or non of the property names match, an empty list is returned. +QList Debugger::retrieveArgumentsFromContext(const QStringList &path, int frame) +{ + QList args; + + if (state() != Paused) + return args; + + if (frame < 0) + return args; + + CallContext *ctxt = findContext(m_engine->current, frame); + if (!ctxt) + return args; + + Scope scope(m_engine); + ScopedValue v(scope); + for (unsigned i = 0, ei = ctxt->formalCount(); i != ei; ++i) { + // value = ctxt->argument(i); + String *name = ctxt->formals()[i]; + QString qName; + if (name) + qName = name->toQString(); + if (path.isEmpty()) { + v = ctxt->argument(i); + QVariant value; + VarInfo::Type type; + convert(v, &value, &type); + args.append(VarInfo(qName, value, type)); + } else if (path.first() == qName) { + ScopedObject o(scope, ctxt->argument(i)); + QStringList pathTail = path; + pathTail.pop_front(); + return retrieveFromValue(o, pathTail); + } + } + + return args; +} + +/// Same as \c retrieveArgumentsFromContext, but now for locals. +QList Debugger::retrieveLocalsFromContext(const QStringList &path, int frame) +{ + QList args; + + if (state() != Paused) + return args; + + if (frame < 0) + return args; + + CallContext *ctxt = findContext(m_engine->current, frame); + if (!ctxt) + return args; + + Scope scope(m_engine); + ScopedValue v(scope); + for (unsigned i = 0, ei = ctxt->variableCount(); i != ei; ++i) { + String *name = ctxt->variables()[i]; + QString qName; + if (name) + qName = name->toQString(); + if (path.isEmpty()) { + v = ctxt->locals[i]; + QVariant value; + VarInfo::Type type; + convert(v, &value, &type); + args.append(VarInfo(qName, value, type)); + } else if (path.first() == qName) { + ScopedObject o(scope, ctxt->locals[i]); + QStringList pathTail = path; + pathTail.pop_front(); + return retrieveFromValue(o, pathTail); + } + } + + return args; +} + void Debugger::maybeBreakAtInstruction(const uchar *code, bool breakPointHit) { QMutexLocker locker(&m_lock); @@ -187,7 +369,7 @@ void Debugger::pauseAndWait() void Debugger::applyPendingBreakPoints() { - foreach (QV4::CompiledData::CompilationUnit *unit, _engine->compilationUnits) { + foreach (QV4::CompiledData::CompilationUnit *unit, m_engine->compilationUnits) { foreach (Function *function, unit->runtimeFunctions) { m_pendingBreakPointsToAdd.applyToFunction(function, /*removeBreakPoints*/false); m_pendingBreakPointsToRemove.applyToFunction(function, /*removeBreakPoints*/true); @@ -205,92 +387,6 @@ void Debugger::applyPendingBreakPoints() m_havePendingBreakPoints = false; } -static void realDumpValue(const QV4::ValueRef v, QV4::ExecutionContext *ctx, std::string prefix) -{ - using namespace QV4; - using namespace std; - - Scope scope(ctx); - - cout << prefix << "tag: " << hex << v->tag << dec << endl << prefix << "\t-> "; - switch (v->type()) { - case Value::Undefined_Type: cout << "Undefined"; return; - case Value::Null_Type: cout << "Null"; return; - case Value::Boolean_Type: cout << "Boolean"; break; - case Value::Integer_Type: cout << "Integer"; break; - case Value::Managed_Type: cout << v->managed()->className().toUtf8().data(); break; - default: cout << "UNKNOWN" << endl; return; - } - cout << endl; - - if (v->isBoolean()) { - cout << prefix << "\t-> " << (v->booleanValue() ? "TRUE" : "FALSE") << endl; - return; - } - - if (v->isInteger()) { - cout << prefix << "\t-> " << v->integerValue() << endl; - return; - } - - if (v->isDouble()) { - cout << prefix << "\t-> " << v->doubleValue() << endl; - return; - } - - if (v->isString()) { - // maybe check something on the Managed object? - cout << prefix << "\t-> @" << hex << v->stringValue() << endl; - cout << prefix << "\t-> \"" << qPrintable(v->stringValue()->toQString()) << "\"" << endl; - return; - } - - ScopedObject o(scope, v); - if (!o) - return; - - cout << prefix << "\t-> @" << hex << o << endl; - cout << prefix << "object type: " << o->internalType() << endl << prefix << "\t-> "; - switch (o->internalType()) { - case QV4::Managed::Type_Invalid: cout << "Invalid"; break; - case QV4::Managed::Type_String: cout << "String"; break; - case QV4::Managed::Type_Object: cout << "Object"; break; - case QV4::Managed::Type_ArrayObject: cout << "ArrayObject"; break; - case QV4::Managed::Type_FunctionObject: cout << "FunctionObject"; break; - case QV4::Managed::Type_BooleanObject: cout << "BooleanObject"; break; - case QV4::Managed::Type_NumberObject: cout << "NumberObject"; break; - case QV4::Managed::Type_StringObject: cout << "StringObject"; break; - case QV4::Managed::Type_DateObject: cout << "DateObject"; break; - case QV4::Managed::Type_RegExpObject: cout << "RegExpObject"; break; - case QV4::Managed::Type_ErrorObject: cout << "ErrorObject"; break; - case QV4::Managed::Type_ArgumentsObject: cout << "ArgumentsObject"; break; - case QV4::Managed::Type_JSONObject: cout << "JSONObject"; break; - case QV4::Managed::Type_MathObject: cout << "MathObject"; break; - case QV4::Managed::Type_ForeachIteratorObject: cout << "ForeachIteratorObject"; break; - default: cout << "UNKNOWN" << endl; return; - } - cout << endl; - - cout << prefix << "properties:" << endl; - ForEachIteratorObject it(ctx, o); - ScopedValue name(scope); - ScopedValue pval(scope); - for (name = it.nextPropertyName(); !name->isNull(); name = it.nextPropertyName()) { - cout << prefix << "\t\"" << qPrintable(name->stringValue()->toQString()) << "\"" << endl; - PropertyAttributes attrs; - Property *d = o->__getOwnProperty__(ScopedString(scope, name), &attrs); - pval = o->getValue(d, attrs); - cout << prefix << "\tvalue:" << endl; - realDumpValue(pval, ctx, prefix + "\t"); - } -} - -void dumpValue(const QV4::ValueRef v, QV4::ExecutionContext *ctx) -{ - realDumpValue(v, ctx, std::string("")); -} - - void DebuggerAgent::addDebugger(Debugger *debugger) { Q_ASSERT(!m_debuggers.contains(debugger)); @@ -315,6 +411,13 @@ void DebuggerAgent::pauseAll() const pause(debugger); } +void DebuggerAgent::resumeAll() const +{ + foreach (Debugger *debugger, m_debuggers) + if (debugger->state() == Debugger::Paused) + debugger->resume(); +} + void DebuggerAgent::addBreakPoint(const QString &fileName, int lineNumber) const { foreach (Debugger *debugger, m_debuggers) diff --git a/src/qml/jsruntime/qv4debugging_p.h b/src/qml/jsruntime/qv4debugging_p.h index e44f415..133cc3e 100644 --- a/src/qml/jsruntime/qv4debugging_p.h +++ b/src/qml/jsruntime/qv4debugging_p.h @@ -64,12 +64,35 @@ class DebuggerAgent; class Q_QML_EXPORT Debugger { public: + struct VarInfo { + enum Type { + Invalid = 0, + Undefined = 1, + Null, + Number, + String, + Bool, + Object + }; + + QString name; + QVariant value; + Type type; + + VarInfo(): type(Invalid) {} + VarInfo(const QString &name, const QVariant &value, Type type) + : name(name), value(value), type(type) + {} + + bool isValid() const { return type != Invalid; } + }; + enum State { Running, Paused }; - Debugger(ExecutionEngine *_engine); + Debugger(ExecutionEngine *engine); ~Debugger(); void attachToAgent(DebuggerAgent *agent); @@ -98,6 +121,10 @@ public: } void setPendingBreakpoints(Function *function); + QVector stackTrace(int frameLimit = -1) const; + QList retrieveArgumentsFromContext(const QStringList &path, int frame = 0); + QList retrieveLocalsFromContext(const QStringList &path, int frame = 0); + public: // compile-time interface void maybeBreakAtInstruction(const uchar *code, bool breakPointHit); @@ -110,6 +137,9 @@ private: void applyPendingBreakPoints(); + QList retrieveFromValue(const ObjectRef o, const QStringList &path) const; + void convert(ValueRef v, QVariant *varValue, VarInfo::Type *type) const; + struct BreakPoints : public QHash > { void add(const QString &fileName, int lineNumber); @@ -118,7 +148,7 @@ private: void applyToFunction(Function *function, bool removeBreakPoints); }; - QV4::ExecutionEngine *_engine; + QV4::ExecutionEngine *m_engine; DebuggerAgent *m_agent; QMutex m_lock; QWaitCondition m_runningCondition; @@ -142,6 +172,7 @@ public: void pause(Debugger *debugger) const; void pauseAll() const; + void resumeAll() const; void addBreakPoint(const QString &fileName, int lineNumber) const; void removeBreakPoint(const QString &fileName, int lineNumber) const; diff --git a/src/qml/jsruntime/qv4functionobject.cpp b/src/qml/jsruntime/qv4functionobject.cpp index 55baef0..3e28024 100644 --- a/src/qml/jsruntime/qv4functionobject.cpp +++ b/src/qml/jsruntime/qv4functionobject.cpp @@ -267,7 +267,7 @@ ReturnedValue FunctionCtor::construct(Managed *that, CallData *callData) if (!fe) v4->current->throwSyntaxError(0); - QQmlJS::V4IR::Module module; + QQmlJS::V4IR::Module module(v4->debugger != 0); QQmlJS::RuntimeCodegen cg(v4->current, f->strictMode); cg.generateFromFunctionExpression(QString(), function, fe, &module); diff --git a/src/qml/jsruntime/qv4script.cpp b/src/qml/jsruntime/qv4script.cpp index d301321..daca700 100644 --- a/src/qml/jsruntime/qv4script.cpp +++ b/src/qml/jsruntime/qv4script.cpp @@ -173,7 +173,7 @@ void Script::parse() MemoryManager::GCBlocker gcBlocker(v4->memoryManager); - V4IR::Module module; + V4IR::Module module(v4->debugger != 0); QQmlJS::Engine ee, *engine = ⅇ Lexer lexer(engine); @@ -308,7 +308,7 @@ CompiledData::CompilationUnit *Script::precompile(ExecutionEngine *engine, const using namespace QQmlJS; using namespace QQmlJS::AST; - QQmlJS::V4IR::Module module; + QQmlJS::V4IR::Module module(engine->debugger != 0); QQmlJS::Engine ee; QQmlJS::Lexer lexer(&ee); diff --git a/src/qml/qml/qqmlcompiler.cpp b/src/qml/qml/qqmlcompiler.cpp index 2cfb074..57114eb 100644 --- a/src/qml/qml/qqmlcompiler.cpp +++ b/src/qml/qml/qqmlcompiler.cpp @@ -810,7 +810,7 @@ bool QQmlCompiler::compile(QQmlEngine *engine, this->unit = unit; this->unitRoot = root; this->output = out; - this->jsModule.reset(new QQmlJS::V4IR::Module); + this->jsModule.reset(new QQmlJS::V4IR::Module(enginePrivate->v4engine()->debugger)); this->jsModule->isQmlModule = true; // Compile types diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index 601c1b8..d7ba157 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -2141,7 +2141,7 @@ void QQmlTypeData::dataReceived(const Data &data) if (data.isFile()) preparseData = data.asFile()->metaData(QLatin1String("qml:preparse")); if (m_useNewCompiler) { - parsedQML.reset(new QtQml::ParsedQML); + parsedQML.reset(new QtQml::ParsedQML(QV8Engine::getV4(typeLoader()->engine())->debugger != 0)); QQmlCodeGenerator compiler; if (!compiler.generateFromQml(code, finalUrl(), finalUrlString(), parsedQML.data())) { setError(compiler.errors); diff --git a/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp b/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp index 9fbcb4a..27091e5 100644 --- a/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp +++ b/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp @@ -99,12 +99,15 @@ class TestAgent : public QV4::Debugging::DebuggerAgent { Q_OBJECT public: + typedef QV4::Debugging::Debugger Debugger; + TestAgent() : m_wasPaused(false) + , m_captureContextInfo(false) { } - virtual void debuggerPaused(QV4::Debugging::Debugger *debugger) + virtual void debuggerPaused(Debugger *debugger) { Q_ASSERT(m_debuggers.count() == 1 && m_debuggers.first() == debugger); m_wasPaused = true; @@ -114,6 +117,9 @@ public: debugger->addBreakPoint(bp.fileName, bp.lineNumber); m_breakPointsToAddWhenPaused.clear(); + if (m_captureContextInfo) + captureContextInfo(debugger); + debugger->resume(); } @@ -128,18 +134,49 @@ public: int lineNumber; }; + void captureContextInfo(Debugger *debugger) + { + m_stackTrace = debugger->stackTrace(); + for (int i = 0, ei = m_stackTrace.size(); i != ei; ++i) { + m_capturedArguments.append(debugger->retrieveArgumentsFromContext(QStringList(), i)); + m_capturedLocals.append(debugger->retrieveLocalsFromContext(QStringList(), i)); + } + + foreach (const QStringList &path, m_localPathsToRead) + m_localPathResults += debugger->retrieveLocalsFromContext(path); + } + bool m_wasPaused; + bool m_captureContextInfo; QList m_statesWhenPaused; QList m_breakPointsToAddWhenPaused; + QVector m_stackTrace; + QList > m_capturedArguments; + QList > m_capturedLocals; + QList m_localPathsToRead; + QList > m_localPathResults; + + // Utility methods: + void dumpStackTrace() const + { + qDebug() << "Stack depth:" << m_stackTrace.size(); + foreach (const QV4::StackFrame &frame, m_stackTrace) + qDebug("\t%s (%s:%d:%d)", qPrintable(frame.function), qPrintable(frame.source), + frame.line, frame.column); + } }; class tst_qv4debugger : public QObject { Q_OBJECT + + typedef QV4::Debugging::Debugger::VarInfo VarInfo; + private slots: void init(); void cleanup(); + // breakpoints: void breakAnywhere(); void pendingBreakpoint(); void liveBreakPoint(); @@ -147,6 +184,12 @@ private slots: void addBreakPointWhilePaused(); void removeBreakPointForNextInstruction(); + // context access: + void readArguments(); + void readLocals(); + void readObject(); + void readContextInAllFrames(); + private: void evaluateJavaScript(const QString &script, const QString &fileName, int lineNumber = 1) { @@ -283,6 +326,128 @@ void tst_qv4debugger::removeBreakPointForNextInstruction() QVERIFY(!m_debuggerAgent->m_wasPaused); } +void tst_qv4debugger::readArguments() +{ + m_debuggerAgent->m_captureContextInfo = true; + QString script = + "function f(a, b, c, d) {\n" + " return a === b\n" + "}\n" + "var four;\n" + "f(1, 'two', null, four);\n"; + m_debuggerAgent->addBreakPoint("readArguments", 2); + evaluateJavaScript(script, "readArguments"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_capturedArguments[0].size(), 4); + QCOMPARE(m_debuggerAgent->m_capturedArguments[0][0].name, QString("a")); + QCOMPARE(m_debuggerAgent->m_capturedArguments[0][0].type, VarInfo::Number); + QCOMPARE(m_debuggerAgent->m_capturedArguments[0][0].value.toDouble(), 1.0); + QCOMPARE(m_debuggerAgent->m_capturedArguments[0][1].name, QString("b")); + QCOMPARE(m_debuggerAgent->m_capturedArguments[0][1].type, VarInfo::String); + QCOMPARE(m_debuggerAgent->m_capturedArguments[0][1].value.toString(), QLatin1String("two")); + QCOMPARE(m_debuggerAgent->m_capturedArguments[0][2].name, QString("c")); + QCOMPARE(m_debuggerAgent->m_capturedArguments[0][2].type, VarInfo::Null); + QCOMPARE(m_debuggerAgent->m_capturedArguments[0][3].name, QString("d")); + QCOMPARE(m_debuggerAgent->m_capturedArguments[0][3].type, VarInfo::Undefined); +} + +void tst_qv4debugger::readLocals() +{ + m_debuggerAgent->m_captureContextInfo = true; + QString script = + "function f(a, b) {\n" + " var c = a + b\n" + " var d = a - b\n" // breakpoint, c should be set, d should be undefined + " return c === d\n" + "}\n" + "f(1, 2, 3);\n"; + m_debuggerAgent->addBreakPoint("readLocals", 3); + evaluateJavaScript(script, "readLocals"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_capturedLocals[0].size(), 2); + QCOMPARE(m_debuggerAgent->m_capturedLocals[0][0].name, QString("c")); + QCOMPARE(m_debuggerAgent->m_capturedLocals[0][0].type, VarInfo::Number); + QCOMPARE(m_debuggerAgent->m_capturedLocals[0][0].value.toDouble(), 3.0); + QCOMPARE(m_debuggerAgent->m_capturedLocals[0][1].name, QString("d")); + QCOMPARE(m_debuggerAgent->m_capturedLocals[0][1].type, VarInfo::Undefined); +} + +void tst_qv4debugger::readObject() +{ + m_debuggerAgent->m_captureContextInfo = true; + QString script = + "function f(a) {\n" + " var b = a\n" + " return b\n" + "}\n" + "f({head: 1, tail: { head: 'asdf', tail: null }});\n"; + m_debuggerAgent->addBreakPoint("readObject", 3); + m_debuggerAgent->m_localPathsToRead.append(QStringList() << QLatin1String("b")); + m_debuggerAgent->m_localPathsToRead.append(QStringList() << QLatin1String("b") << QLatin1String("tail")); + evaluateJavaScript(script, "readObject"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_capturedLocals[0].size(), 1); + QCOMPARE(m_debuggerAgent->m_capturedLocals[0][0].name, QString("b")); + QCOMPARE(m_debuggerAgent->m_capturedLocals[0][0].type, VarInfo::Object); + + QCOMPARE(m_debuggerAgent->m_localPathResults.size(), 2); + + QList b = m_debuggerAgent->m_localPathResults[0]; + QCOMPARE(b.size(), 2); + QCOMPARE(b[0].name, QLatin1String("head")); + QCOMPARE(b[0].type, VarInfo::Number); + QCOMPARE(b[0].value.toDouble(), 1.0); + QCOMPARE(b[1].name, QLatin1String("tail")); + QCOMPARE(b[1].type, VarInfo::Object); + + QList b_tail = m_debuggerAgent->m_localPathResults[1]; + QCOMPARE(b_tail.size(), 2); + QCOMPARE(b_tail[0].name, QLatin1String("head")); + QCOMPARE(b_tail[0].type, VarInfo::String); + QCOMPARE(b_tail[0].value.toString(), QLatin1String("asdf")); + QCOMPARE(b_tail[1].name, QLatin1String("tail")); + QCOMPARE(b_tail[1].type, VarInfo::Null); +} + +void tst_qv4debugger::readContextInAllFrames() +{ + m_debuggerAgent->m_captureContextInfo = true; + QString script = + "function fact(n) {\n" + " if (n > 1) {\n" + " var n_1 = n - 1;\n" + " n_1 = fact(n_1);\n" + " return n * n_1;\n" + " } else\n" + " return 1;\n" // breakpoint + "}\n" + "fact(12);\n"; + m_debuggerAgent->addBreakPoint("readFormalsInAllFrames", 7); + evaluateJavaScript(script, "readFormalsInAllFrames"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_stackTrace.size(), 13); + QCOMPARE(m_debuggerAgent->m_capturedArguments.size(), 13); + QCOMPARE(m_debuggerAgent->m_capturedLocals.size(), 13); + + for (int i = 0; i < 12; ++i) { + QCOMPARE(m_debuggerAgent->m_capturedArguments[i].size(), 1); + QCOMPARE(m_debuggerAgent->m_capturedArguments[i][0].name, QString("n")); + QCOMPARE(m_debuggerAgent->m_capturedArguments[i][0].type, VarInfo::Number); + QCOMPARE(m_debuggerAgent->m_capturedArguments[i][0].value.toInt(), i + 1); + + QCOMPARE(m_debuggerAgent->m_capturedLocals[i].size(), 1); + QCOMPARE(m_debuggerAgent->m_capturedLocals[i][0].name, QString("n_1")); + if (i == 0) { + QCOMPARE(m_debuggerAgent->m_capturedLocals[i][0].type, VarInfo::Undefined); + } else { + QCOMPARE(m_debuggerAgent->m_capturedLocals[i][0].type, VarInfo::Number); + QCOMPARE(m_debuggerAgent->m_capturedLocals[i][0].value.toInt(), i); + } + } + QCOMPARE(m_debuggerAgent->m_capturedArguments[12].size(), 0); + QCOMPARE(m_debuggerAgent->m_capturedLocals[12].size(), 0); +} + QTEST_MAIN(tst_qv4debugger) #include "tst_qv4debugger.moc" -- 2.7.4