diff options
Diffstat (limited to 'src/qml/jsruntime/qv4debugging.cpp')
-rw-r--r-- | src/qml/jsruntime/qv4debugging.cpp | 800 |
1 files changed, 666 insertions, 134 deletions
diff --git a/src/qml/jsruntime/qv4debugging.cpp b/src/qml/jsruntime/qv4debugging.cpp index 41ed34ea18..95b4100651 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 <iostream> #include <algorithm> @@ -51,14 +52,81 @@ using namespace QV4; using namespace QV4::Debugging; +namespace { +class EvalJob: public Debugger::Job +{ + QV4::ExecutionEngine *engine; + const QString &script; + +public: + EvalJob(QV4::ExecutionEngine *engine, const QString &script) + : engine(engine) + , script(script) + {} + + ~EvalJob() {} + + void run() + { + // TODO + qDebug() << "Evaluating script:" << script; + Q_UNUSED(engine); + } + + bool resultAsBoolean() const + { + return true; + } +}; + +class GatherSourcesJob: public Debugger::Job +{ + QV4::ExecutionEngine *engine; + const int seq; + +public: + GatherSourcesJob(QV4::ExecutionEngine *engine, int seq) + : engine(engine) + , seq(seq) + {} + + ~GatherSourcesJob() {} + + void run() + { + QStringList sources; + + foreach (QV4::CompiledData::CompilationUnit *unit, engine->compilationUnits) { + QString fileName = unit->fileName(); + if (!fileName.isEmpty()) + sources.append(fileName); + } + + Debugger *debugger = engine->debugger; + QMetaObject::invokeMethod(debugger->agent(), "sourcesCollected", Qt::QueuedConnection, + Q_ARG(QV4::Debugging::Debugger*, debugger), + Q_ARG(QStringList, sources), + Q_ARG(int, seq)); + } +}; +} + Debugger::Debugger(QV4::ExecutionEngine *engine) - : _engine(engine) + : m_engine(engine) , m_agent(0) , m_state(Running) , m_pauseRequested(false) + , m_gatherSources(0) + , m_havePendingBreakPoints(false) , m_currentInstructionPointer(0) + , m_stepping(NotStepping) + , m_stopForStepping(false) + , m_returnedValue(Primitive::undefinedValue()) + , m_breakOnThrow(false) + , m_runningJob(0) { qMetaTypeId<Debugger*>(); + qMetaTypeId<PauseReason>(); } Debugger::~Debugger() @@ -84,6 +152,18 @@ void Debugger::detachFromAgent() agent->removeDebugger(this); } +void Debugger::gatherSources(int requestSequenceNr) +{ + QMutexLocker locker(&m_lock); + + m_gatherSources = new GatherSourcesJob(m_engine, requestSequenceNr); + if (m_state == Paused) { + runInEngine_havingLock(m_gatherSources); + delete m_gatherSources; + m_gatherSources = 0; + } +} + void Debugger::pause() { QMutexLocker locker(&m_lock); @@ -92,19 +172,33 @@ void Debugger::pause() m_pauseRequested = true; } -void Debugger::resume() +void Debugger::resume(Speed speed) { QMutexLocker locker(&m_lock); - Q_ASSERT(m_state == Paused); + if (m_state != Paused) + return; + + if (!m_returnedValue.isUndefined()) + m_returnedValue = Primitive::undefinedValue(); + + clearTemporaryBreakPoints(); + if (speed == StepOver) + setTemporaryBreakPointOnNextLine(); + if (speed == StepOut) + m_temporaryBreakPoints = TemporaryBreakPoint(getFunction(), m_engine->current); + + m_stepping = speed; m_runningCondition.wakeAll(); } -void Debugger::addBreakPoint(const QString &fileName, int lineNumber) +void Debugger::addBreakPoint(const QString &fileName, int lineNumber, const QString &condition) { QMutexLocker locker(&m_lock); if (!m_pendingBreakPointsToRemove.remove(fileName, lineNumber)) m_pendingBreakPointsToAdd.add(fileName, lineNumber); m_havePendingBreakPoints = !m_pendingBreakPointsToAdd.isEmpty() || !m_pendingBreakPointsToRemove.isEmpty(); + if (!condition.isEmpty()) + m_breakPointConditions.add(fileName, lineNumber, condition); } void Debugger::removeBreakPoint(const QString &fileName, int lineNumber) @@ -113,6 +207,14 @@ void Debugger::removeBreakPoint(const QString &fileName, int lineNumber) if (!m_pendingBreakPointsToAdd.remove(fileName, lineNumber)) m_pendingBreakPointsToRemove.add(fileName, lineNumber); m_havePendingBreakPoints = !m_pendingBreakPointsToAdd.isEmpty() || !m_pendingBreakPointsToRemove.isEmpty(); + m_breakPointConditions.remove(fileName, lineNumber); +} + +void Debugger::setBreakOnThrow(bool onoff) +{ + QMutexLocker locker(&m_lock); + + m_breakOnThrow = onoff; } Debugger::ExecutionState Debugger::currentExecutionState(const uchar *code) const @@ -122,20 +224,11 @@ Debugger::ExecutionState Debugger::currentExecutionState(const uchar *code) cons // ### Locking ExecutionState state; - QV4::ExecutionContext *context = _engine->current; - QV4::Function *function = 0; - if (CallContext *callCtx = context->asCallContext()) - function = callCtx->function->function; - else { - Q_ASSERT(context->type == QV4::ExecutionContext::Type_GlobalContext); - function = context->engine->globalCode; - } + state.function = getFunction(); + state.fileName = state.function->sourceFile(); - state.function = function; - state.fileName = function->sourceFile(); - - qptrdiff relativeProgramCounter = code - function->codeData; - state.lineNumber = function->lineNumberForProgramCounter(relativeProgramCounter); + qptrdiff relativeProgramCounter = code - state.function->codeData; + state.lineNumber = state.function->lineNumberForProgramCounter(relativeProgramCounter); return state; } @@ -145,49 +238,395 @@ void Debugger::setPendingBreakpoints(Function *function) m_pendingBreakPointsToAddToFutureCode.applyToFunction(function, /*removeBreakPoints*/ false); } +QVector<StackFrame> Debugger::stackTrace(int frameLimit) const +{ + return m_engine->stackTrace(frameLimit); +} + +static inline CallContext *findContext(ExecutionContext *ctxt, int frame) +{ + while (ctxt) { + CallContext *cCtxt = ctxt->asCallContext(); + if (cCtxt && cCtxt->function) { + if (frame < 1) + return cCtxt; + --frame; + } + ctxt = ctxt->parent; + } + + return 0; +} + +static inline CallContext *findScope(ExecutionContext *ctxt, int scope) +{ + for (; scope > 0 && ctxt; --scope) + ctxt = ctxt->outer; + + return ctxt ? ctxt->asCallContext() : 0; +} + +void Debugger::collectArgumentsInContext(Collector *collector, int frameNr, int scopeNr) +{ + if (state() != Paused) + return; + + class ArgumentCollectJob: public Job + { + QV4::ExecutionEngine *engine; + Collector *collector; + int frameNr; + int scopeNr; + + public: + ArgumentCollectJob(QV4::ExecutionEngine *engine, Collector *collector, int frameNr, int scopeNr) + : engine(engine) + , collector(collector) + , frameNr(frameNr) + , scopeNr(scopeNr) + {} + + ~ArgumentCollectJob() {} + + void run() + { + if (frameNr < 0) + return; + + CallContext *ctxt = findScope(findContext(engine->current, frameNr), scopeNr); + if (!ctxt) + return; + + Scope scope(engine); + ScopedValue v(scope); + for (unsigned i = 0, ei = ctxt->formalCount(); i != ei; ++i) { + QString qName; + if (String *name = ctxt->formals()[i]) + qName = name->toQString(); + v = ctxt->argument(i); + collector->collect(qName, v); + } + } + }; + + ArgumentCollectJob job(m_engine, collector, frameNr, scopeNr); + runInEngine(&job); +} + +/// Same as \c retrieveArgumentsFromContext, but now for locals. +void Debugger::collectLocalsInContext(Collector *collector, int frameNr, int scopeNr) +{ + if (state() != Paused) + return; + + class LocalCollectJob: public Job + { + QV4::ExecutionEngine *engine; + Collector *collector; + int frameNr; + int scopeNr; + + public: + LocalCollectJob(QV4::ExecutionEngine *engine, Collector *collector, int frameNr, int scopeNr) + : engine(engine) + , collector(collector) + , frameNr(frameNr) + , scopeNr(scopeNr) + {} + + void run() + { + if (frameNr < 0) + return; + + CallContext *ctxt = findScope(findContext(engine->current, frameNr), scopeNr); + if (!ctxt) + return; + + Scope scope(engine); + ScopedValue v(scope); + for (unsigned i = 0, ei = ctxt->variableCount(); i != ei; ++i) { + QString qName; + if (String *name = ctxt->variables()[i]) + qName = name->toQString(); + v = ctxt->locals[i]; + collector->collect(qName, v); + } + } + }; + + LocalCollectJob job(m_engine, collector, frameNr, scopeNr); + runInEngine(&job); +} + +bool Debugger::collectThisInContext(Debugger::Collector *collector, int frame) +{ + if (state() != Paused) + return false; + + class ThisCollectJob: public Job + { + QV4::ExecutionEngine *engine; + Collector *collector; + int frameNr; + bool *foundThis; + + public: + ThisCollectJob(QV4::ExecutionEngine *engine, Collector *collector, int frameNr, bool *foundThis) + : engine(engine) + , collector(collector) + , frameNr(frameNr) + , foundThis(foundThis) + {} + + void run() + { + *foundThis = myRun(); + } + + bool myRun() + { + ExecutionContext *ctxt = findContext(engine->current, frameNr); + while (ctxt) { + if (CallContext *cCtxt = ctxt->asCallContext()) + if (cCtxt->activation) + break; + ctxt = ctxt->outer; + } + + if (!ctxt) + return false; + + Scope scope(engine); + ScopedObject o(scope, ctxt->asCallContext()->activation); + collector->collect(o); + return true; + } + }; + + bool foundThis = false; + ThisCollectJob job(m_engine, collector, frame, &foundThis); + runInEngine(&job); + return foundThis; +} + +void Debugger::collectThrownValue(Collector *collector) +{ + if (state() != Paused || !m_engine->hasException) + return; + + class ThisCollectJob: public Job + { + QV4::ExecutionEngine *engine; + Collector *collector; + + public: + ThisCollectJob(QV4::ExecutionEngine *engine, Collector *collector) + : engine(engine) + , collector(collector) + {} + + void run() + { + Scope scope(engine); + ScopedValue v(scope, engine->exceptionValue); + collector->collect(QStringLiteral("exception"), v); + } + }; + + ThisCollectJob job(m_engine, collector); + runInEngine(&job); +} + +void Debugger::collectReturnedValue(Collector *collector) const +{ + if (state() != Paused) + return; + + Scope scope(m_engine); + ScopedObject o(scope, m_returnedValue); + collector->collect(o); +} + +QVector<ExecutionContext::Type> Debugger::getScopeTypes(int frame) const +{ + QVector<ExecutionContext::Type> types; + + if (state() != Paused) + return types; + + CallContext *sctxt = findContext(m_engine->current, frame); + if (!sctxt || sctxt->type < ExecutionContext::Type_SimpleCallContext) + return types; + CallContext *ctxt = static_cast<CallContext *>(sctxt); + + for (ExecutionContext *it = ctxt; it; it = it->outer) + types.append(it->type); + + return types; +} + void Debugger::maybeBreakAtInstruction(const uchar *code, bool breakPointHit) { + if (m_runningJob) // do not re-enter when we're doing a job for the debugger. + return; + QMutexLocker locker(&m_lock); m_currentInstructionPointer = code; + ExecutionState state = currentExecutionState(); + // Do debugger internal work if (m_havePendingBreakPoints) { - - if (breakPointHit) { - ExecutionState state = currentExecutionState(); + if (breakPointHit) breakPointHit = !m_pendingBreakPointsToRemove.contains(state.fileName, state.lineNumber); - } applyPendingBreakPoints(); } - // Serve debugging requests from the agent - if (m_pauseRequested) { + if (m_gatherSources) { + m_gatherSources->run(); + delete m_gatherSources; + m_gatherSources = 0; + } + + if (m_stopForStepping) { + clearTemporaryBreakPoints(); + m_stopForStepping = false; + m_pauseRequested = false; + pauseAndWait(Step); + } else if (m_pauseRequested) { // Serve debugging requests from the agent m_pauseRequested = false; - pauseAndWait(); - } else if (breakPointHit) - pauseAndWait(); + pauseAndWait(PauseRequest); + } else if (breakPointHit) { + if (m_stepping == StepOver && m_temporaryBreakPoints.context == m_engine->current) + pauseAndWait(Step); + else if (reallyHitTheBreakPoint(state.fileName, state.lineNumber)) + pauseAndWait(BreakPoint); + } if (!m_pendingBreakPointsToAdd.isEmpty() || !m_pendingBreakPointsToRemove.isEmpty()) applyPendingBreakPoints(); } -void Debugger::aboutToThrow(const QV4::ValueRef value) +void Debugger::enteringFunction() +{ + QMutexLocker locker(&m_lock); + + if (m_stepping == StepIn) { + m_stepping = NotStepping; + m_stopForStepping = true; + m_pauseRequested = true; + } +} + +void Debugger::leavingFunction(const ReturnedValue &retVal) { - qDebug() << "*** We are about to throw..."; + Q_UNUSED(retVal); // TODO + + QMutexLocker locker(&m_lock); + + if ((m_stepping == StepOut || m_stepping == StepOver) + && temporaryBreakPointInFunction(m_engine->current)) { + clearTemporaryBreakPoints(); + m_stepping = NotStepping; + m_stopForStepping = true; + m_pauseRequested = true; + m_returnedValue = retVal; + } +} + +void Debugger::aboutToThrow() +{ + if (!m_breakOnThrow) + return; + + if (m_runningJob) // do not re-enter when we're doing a job for the debugger. + return; + + QMutexLocker locker(&m_lock); + clearTemporaryBreakPoints(); + pauseAndWait(Throwing); +} + +Function *Debugger::getFunction() const +{ + ExecutionContext *context = m_engine->current; + if (CallContext *callCtx = context->asCallContext()) + return callCtx->function->function; + else { + Q_ASSERT(context->type == QV4::ExecutionContext::Type_GlobalContext); + return context->engine->globalCode; + } } -void Debugger::pauseAndWait() +void Debugger::pauseAndWait(PauseReason reason) { + if (m_runningJob) + return; + m_state = Paused; - QMetaObject::invokeMethod(m_agent, "debuggerPaused", Qt::QueuedConnection, Q_ARG(QV4::Debugging::Debugger*, this)); - m_runningCondition.wait(&m_lock); + QMetaObject::invokeMethod(m_agent, "debuggerPaused", Qt::QueuedConnection, + Q_ARG(QV4::Debugging::Debugger*, this), + Q_ARG(QV4::Debugging::PauseReason, reason)); + + while (true) { + m_runningCondition.wait(&m_lock); + if (m_runningJob) { + m_runningJob->run(); + m_jobIsRunning.wakeAll(); + } else { + break; + } + } + m_state = Running; } +void Debugger::setTemporaryBreakPointOnNextLine() +{ + ExecutionState state = currentExecutionState(); + Function *function = state.function; + if (!function) + return; + + QList<qptrdiff> pcs = function->programCountersForAllLines(); + if (pcs.isEmpty()) + return; + + m_temporaryBreakPoints = TemporaryBreakPoint(function, m_engine->current); + m_temporaryBreakPoints.codeOffsets.reserve(pcs.size()); + for (QList<qptrdiff>::const_iterator i = pcs.begin(), ei = pcs.end(); i != ei; ++i) { + // note: we do set a breakpoint on the current line, because there could be a loop where + // a step-over would be jump back to the first instruction making up the current line. + qptrdiff offset = *i; + + if (hasBreakOnInstruction(function, offset)) + continue; // do not set a temporary breakpoint if there already is a breakpoint set by the user + + setBreakOnInstruction(function, offset, true); + m_temporaryBreakPoints.codeOffsets.append(offset); + } +} + +void Debugger::clearTemporaryBreakPoints() +{ + if (m_temporaryBreakPoints.function) { + foreach (quintptr offset, m_temporaryBreakPoints.codeOffsets) + setBreakOnInstruction(m_temporaryBreakPoints.function, offset, false); + m_temporaryBreakPoints = TemporaryBreakPoint(); + } +} + +bool Debugger::temporaryBreakPointInFunction(ExecutionContext *context) const +{ + return m_temporaryBreakPoints.function == getFunction() + && m_temporaryBreakPoints.context == context; +} + 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,97 +644,62 @@ void Debugger::applyPendingBreakPoints() m_havePendingBreakPoints = false; } -static void realDumpValue(const QV4::ValueRef v, QV4::ExecutionContext *ctx, std::string prefix) +void Debugger::setBreakOnInstruction(Function *function, qptrdiff codeOffset, bool onoff) { - 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; - } + uchar *codePtr = const_cast<uchar *>(function->codeData) + codeOffset; + QQmlJS::Moth::Instr *instruction = reinterpret_cast<QQmlJS::Moth::Instr*>(codePtr); + instruction->common.breakPoint = onoff; +} - 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; - } +bool Debugger::hasBreakOnInstruction(Function *function, qptrdiff codeOffset) +{ + uchar *codePtr = const_cast<uchar *>(function->codeData) + codeOffset; + QQmlJS::Moth::Instr *instruction = reinterpret_cast<QQmlJS::Moth::Instr*>(codePtr); + return instruction->common.breakPoint; +} - ScopedObject o(scope, v); - if (!o) - return; +bool Debugger::reallyHitTheBreakPoint(const QString &filename, int linenr) +{ + QString condition = m_breakPointConditions.condition(filename, linenr); + if (condition.isEmpty()) + return true; - 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; + Q_ASSERT(m_runningJob == 0); + EvalJob evilJob(m_engine, condition); + m_runningJob = &evilJob; + m_runningJob->run(); - 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"); - } + return evilJob.resultAsBoolean(); } -void dumpValue(const QV4::ValueRef v, QV4::ExecutionContext *ctx) +void Debugger::runInEngine(Debugger::Job *job) { - realDumpValue(v, ctx, std::string("")); + QMutexLocker locker(&m_lock); + runInEngine_havingLock(job); } +void Debugger::runInEngine_havingLock(Debugger::Job *job) +{ + Q_ASSERT(job); + Q_ASSERT(m_runningJob == 0); + + m_runningJob = job; + m_runningCondition.wakeAll(); + m_jobIsRunning.wait(&m_lock); + m_runningJob = 0; +} void DebuggerAgent::addDebugger(Debugger *debugger) { Q_ASSERT(!m_debuggers.contains(debugger)); m_debuggers << debugger; debugger->attachToAgent(this); + + debugger->setBreakOnThrow(m_breakOnThrow); + + foreach (const BreakPoint &breakPoint, m_breakPoints.values()) + if (breakPoint.enabled) + debugger->addBreakPoint(breakPoint.fileName, breakPoint.lineNr, breakPoint.condition); } void DebuggerAgent::removeDebugger(Debugger *debugger) @@ -315,16 +719,77 @@ void DebuggerAgent::pauseAll() const pause(debugger); } -void DebuggerAgent::addBreakPoint(const QString &fileName, int lineNumber) const +void DebuggerAgent::resumeAll() const { foreach (Debugger *debugger, m_debuggers) - debugger->addBreakPoint(fileName, lineNumber); + if (debugger->state() == Debugger::Paused) + debugger->resume(Debugger::FullThrottle); } -void DebuggerAgent::removeBreakPoint(const QString &fileName, int lineNumber) const +int DebuggerAgent::addBreakPoint(const QString &fileName, int lineNumber, bool enabled, const QString &condition) { - foreach (Debugger *debugger, m_debuggers) - debugger->removeBreakPoint(fileName, lineNumber); + if (enabled) + foreach (Debugger *debugger, m_debuggers) + debugger->addBreakPoint(fileName, lineNumber, condition); + + int id = m_breakPoints.size(); + m_breakPoints.insert(id, BreakPoint(fileName, lineNumber, enabled, condition)); + return id; +} + +void DebuggerAgent::removeBreakPoint(int id) +{ + BreakPoint breakPoint = m_breakPoints.value(id); + if (!breakPoint.isValid()) + return; + + m_breakPoints.remove(id); + + if (breakPoint.enabled) + foreach (Debugger *debugger, m_debuggers) + debugger->removeBreakPoint(breakPoint.fileName, breakPoint.lineNr); +} + +void DebuggerAgent::removeAllBreakPoints() +{ + QList<int> ids = m_breakPoints.keys(); + foreach (int id, ids) + removeBreakPoint(id); +} + +void DebuggerAgent::enableBreakPoint(int id, bool onoff) +{ + BreakPoint &breakPoint = m_breakPoints[id]; + if (!breakPoint.isValid() || breakPoint.enabled == onoff) + return; + breakPoint.enabled = onoff; + + foreach (Debugger *debugger, m_debuggers) { + if (onoff) + debugger->addBreakPoint(breakPoint.fileName, breakPoint.lineNr, breakPoint.condition); + else + debugger->removeBreakPoint(breakPoint.fileName, breakPoint.lineNr); + } +} + +QList<int> DebuggerAgent::breakPointIds(const QString &fileName, int lineNumber) const +{ + QList<int> ids; + + for (QHash<int, BreakPoint>::const_iterator i = m_breakPoints.begin(), ei = m_breakPoints.end(); i != ei; ++i) + if (i->lineNr == lineNumber && fileName.endsWith(i->fileName)) + ids.push_back(i.key()); + + return ids; +} + +void DebuggerAgent::setBreakOnThrow(bool onoff) +{ + if (onoff != m_breakOnThrow) { + m_breakOnThrow = onoff; + foreach (Debugger *debugger, m_debuggers) + debugger->setBreakOnThrow(onoff); + } } DebuggerAgent::~DebuggerAgent() @@ -359,32 +824,99 @@ bool Debugger::BreakPoints::contains(const QString &fileName, int lineNumber) co void Debugger::BreakPoints::applyToFunction(Function *function, bool removeBreakPoints) { - Iterator breakPointsForFile = find(function->sourceFile()); - if (breakPointsForFile == end()) - return; + Iterator breakPointsForFile = begin(); - QList<int>::Iterator breakPoint = breakPointsForFile->begin(); - while (breakPoint != breakPointsForFile->end()) { - bool breakPointFound = false; - const quint32 *lineNumberMappings = function->compiledFunction->lineNumberMapping(); - for (int i = 0; i < function->compiledFunction->nLineNumberMappingEntries; ++i) { - const int codeOffset = lineNumberMappings[i * 2]; - const int lineNumber = lineNumberMappings[i * 2 + 1]; - if (lineNumber == *breakPoint) { - uchar *codePtr = const_cast<uchar *>(function->codeData) + codeOffset; - QQmlJS::Moth::Instr *instruction = reinterpret_cast<QQmlJS::Moth::Instr*>(codePtr); - instruction->common.breakPoint = !removeBreakPoints; - // Continue setting the next break point. - breakPointFound = true; - break; + while (breakPointsForFile != end()) { + if (!function->sourceFile().endsWith(breakPointsForFile.key())) { + ++breakPointsForFile; + continue; + } + + QList<int>::Iterator breakPoint = breakPointsForFile->begin(); + while (breakPoint != breakPointsForFile->end()) { + bool breakPointFound = false; + const quint32 *lineNumberMappings = function->compiledFunction->lineNumberMapping(); + for (quint32 i = 0; i < function->compiledFunction->nLineNumberMappingEntries; ++i) { + const int codeOffset = lineNumberMappings[i * 2]; + const int lineNumber = lineNumberMappings[i * 2 + 1]; + if (lineNumber == *breakPoint) { + setBreakOnInstruction(function, codeOffset, !removeBreakPoints); + // Continue setting the next break point. + breakPointFound = true; + break; + } } + if (breakPointFound) + breakPoint = breakPointsForFile->erase(breakPoint); + else + ++breakPoint; } - if (breakPointFound) - breakPoint = breakPointsForFile->erase(breakPoint); + + if (breakPointsForFile->isEmpty()) + breakPointsForFile = erase(breakPointsForFile); + else + ++breakPointsForFile; + } +} + + +Debugger::Collector::~Collector() +{ +} + +void Debugger::Collector::collect(const QString &name, const ScopedValue &value) +{ + switch (value->type()) { + case Value::Empty_Type: + Q_ASSERT(!"empty Value encountered"); + break; + case Value::Undefined_Type: + addUndefined(name); + break; + case Value::Null_Type: + addNull(name); + break; + case Value::Boolean_Type: + addBoolean(name, value->booleanValue()); + break; + case Value::Managed_Type: + if (String *s = value->asString()) + addString(name, s->toQString()); else - ++breakPoint; + addObject(name, value); + break; + case Value::Integer_Type: + addInteger(name, value->int_32); + break; + default: // double + addDouble(name, value->doubleValue()); + break; } +} + +void Debugger::Collector::collect(const ObjectRef object) +{ + bool property = true; + qSwap(property, m_isProperty); - if (breakPointsForFile->isEmpty()) - erase(breakPointsForFile); + Scope scope(m_engine); + ObjectIterator it(scope, object, ObjectIterator::EnumerableOnly); + ScopedValue name(scope); + ScopedValue value(scope); + while (true) { + Value v; + name = it.nextPropertyNameAsString(&v); + if (name->isNull()) + break; + QString key = name->toQStringNoThrow(); + value = v; + collect(key, value); + } + + qSwap(property, m_isProperty); +} + + +Debugger::Job::~Job() +{ } |