From 63ddfee7550b4d12ddb56e4575d8bb27c664c41e Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Wed, 11 Sep 2013 13:46:27 +0200 Subject: 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 +- 9 files changed, 238 insertions(+), 101 deletions(-) (limited to 'src/qml') diff --git a/src/qml/compiler/qqmlcodegenerator_p.h b/src/qml/compiler/qqmlcodegenerator_p.h index 8de08a81d1..1830b62772 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 7b0ee52737..8d090cab1e 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 c9ff6ad53c..d1ebbcc26b 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 41ed34ea18..a29aba8504 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 e44f415da4..133cc3e17c 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 55baef06db..3e28024bbf 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 d30132140b..daca700609 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 2cfb074aae..57114ebead 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 601c1b8bdc..d7ba157f94 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); -- cgit v1.2.3