diff options
author | Simon Hausmann <simon.hausmann@digia.com> | 2013-07-24 10:29:04 +0200 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2013-07-26 11:53:42 +0200 |
commit | 07860794da5863610f38295c9d517fc457c5de95 (patch) | |
tree | 1ea8a4535985b90e8be80857ae086ee10eb15cb2 | |
parent | 993bc84f49b4922480f6ec566f31c24465f0e005 (diff) |
Initial support for debugging in the v4 interpreter
This adds breakpoint support to the Debugger, a helper function in the engine
for enabling debugging (which will switch from JIT to the interpreter) and a
DebuggingAgent interface, for use by v4 clients.
Change-Id: I78e17a6cbe7196b0dfe4ee157fc028532131caa3
Reviewed-by: Erik Verbruggen <erik.verbruggen@digia.com>
-rw-r--r-- | src/qml/qml/v4/moth/qv4instr_moth_p.h | 6 | ||||
-rw-r--r-- | src/qml/qml/v4/moth/qv4isel_moth.cpp | 5 | ||||
-rw-r--r-- | src/qml/qml/v4/moth/qv4vme_moth.cpp | 34 | ||||
-rw-r--r-- | src/qml/qml/v4/qv4codegen.cpp | 19 | ||||
-rw-r--r-- | src/qml/qml/v4/qv4codegen_p.h | 4 | ||||
-rw-r--r-- | src/qml/qml/v4/qv4debugging.cpp | 318 | ||||
-rw-r--r-- | src/qml/qml/v4/qv4debugging_p.h | 154 | ||||
-rw-r--r-- | src/qml/qml/v4/qv4engine.cpp | 14 | ||||
-rw-r--r-- | src/qml/qml/v4/qv4engine_p.h | 6 | ||||
-rw-r--r-- | src/qml/qml/v4/qv4isel_p.cpp | 3 | ||||
-rw-r--r-- | src/qml/qml/v4/qv4script.cpp | 8 | ||||
-rw-r--r-- | tests/auto/qml/qml.pro | 3 | ||||
-rw-r--r-- | tests/auto/qml/qv4debugger/qv4debugger.pro | 7 | ||||
-rw-r--r-- | tests/auto/qml/qv4debugger/tst_qv4debugger.cpp | 287 | ||||
-rw-r--r-- | tools/v4/main.cpp | 14 |
15 files changed, 592 insertions, 290 deletions
diff --git a/src/qml/qml/v4/moth/qv4instr_moth_p.h b/src/qml/qml/v4/moth/qv4instr_moth_p.h index 1ace0172f1..7397a1811d 100644 --- a/src/qml/qml/v4/moth/qv4instr_moth_p.h +++ b/src/qml/qml/v4/moth/qv4instr_moth_p.h @@ -112,9 +112,11 @@ QT_BEGIN_NAMESPACE #define MOTH_INSTR_ALIGN_MASK (Q_ALIGNOF(QQmlJS::Moth::Instr) - 1) #ifdef MOTH_THREADED_INTERPRETER -# define MOTH_INSTR_HEADER void *code; +# define MOTH_INSTR_HEADER void *code; \ + unsigned int breakPoint : 1; #else -# define MOTH_INSTR_HEADER quint8 instructionType; +# define MOTH_INSTR_HEADER quint8 instructionType; \ + unsigned int breakPoint : 1; #endif #define MOTH_INSTR_ENUM(I, FMT) I, diff --git a/src/qml/qml/v4/moth/qv4isel_moth.cpp b/src/qml/qml/v4/moth/qv4isel_moth.cpp index b4045b6dfb..04d759ed8d 100644 --- a/src/qml/qml/v4/moth/qv4isel_moth.cpp +++ b/src/qml/qml/v4/moth/qv4isel_moth.cpp @@ -214,6 +214,7 @@ void InstructionSelection::run(QV4::Function *vmFunction, V4IR::Function *functi int codeSize = 4096; uchar *codeStart = new uchar[codeSize]; + memset(codeStart, 0, codeSize); uchar *codeNext = codeStart; uchar *codeEnd = codeStart + codeSize; @@ -278,6 +279,9 @@ void InstructionSelection::run(QV4::Function *vmFunction, V4IR::Function *functi _vmFunction->code = VME::exec; _vmFunction->codeData = squeezeCode(); + if (QV4::Debugging::Debugger *debugger = engine()->debugger) + debugger->setPendingBreakpoints(_vmFunction); + qSwap(_currentStatement, cs); qSwap(_stackSlotAllocator, stackSlotAllocator); delete stackSlotAllocator; @@ -990,6 +994,7 @@ ptrdiff_t InstructionSelection::addInstructionHelper(Instr::Type type, Instr &in #else instr.common.instructionType = type; #endif + instr.common.breakPoint = 0; int instructionSize = Instr::size(type); if (_codeEnd - _codeNext < instructionSize) { diff --git a/src/qml/qml/v4/moth/qv4vme_moth.cpp b/src/qml/qml/v4/moth/qv4vme_moth.cpp index 527e686592..5a292520eb 100644 --- a/src/qml/qml/v4/moth/qv4vme_moth.cpp +++ b/src/qml/qml/v4/moth/qv4vme_moth.cpp @@ -60,38 +60,11 @@ using namespace QQmlJS; using namespace QQmlJS::Moth; -class FunctionState: public Debugging::FunctionState -{ -public: - FunctionState(QV4::ExecutionContext *context, const uchar **code) - : Debugging::FunctionState(context) - , stack(0) - , stackSize(0) - , code(code) - { - previousInstructionPointer = context->interpreterInstructionPointer; - context->interpreterInstructionPointer = code; - } - ~FunctionState() - { - context()->interpreterInstructionPointer = previousInstructionPointer; - } - - virtual QV4::Value *temp(unsigned idx) { return stack + idx; } - - void setStack(QV4::Value *stack, unsigned stackSize) - { this->stack = stack; this->stackSize = stackSize; } - -private: - QV4::Value *stack; - unsigned stackSize; - const uchar **code; - const uchar **previousInstructionPointer; -}; - #define MOTH_BEGIN_INSTR_COMMON(I) { \ const InstrMeta<(int)Instr::I>::DataType &instr = InstrMeta<(int)Instr::I>::data(*genericInstr); \ code += InstrMeta<(int)Instr::I>::Size; \ + if (context->engine->debugger && (instr.breakPoint || context->engine->debugger->pauseAtNextOpportunity())) \ + context->engine->debugger->maybeBreakAtInstruction(code, instr.breakPoint); \ Q_UNUSED(instr); \ TRACE_INSTR(I) @@ -265,8 +238,6 @@ QV4::Value VME::run(QV4::ExecutionContext *context, const uchar *&code, } #endif - FunctionState state(context, &code); - #ifdef MOTH_THREADED_INTERPRETER const Instr *genericInstr = reinterpret_cast<const Instr *>(code); goto *genericInstr->common.code; @@ -320,7 +291,6 @@ QV4::Value VME::run(QV4::ExecutionContext *context, const uchar *&code, stackSize = instr.value; stack = static_cast<QV4::Value *>(alloca(stackSize * sizeof(QV4::Value))); memset(stack, 0, stackSize * sizeof(QV4::Value)); - state.setStack(stack, stackSize); MOTH_END_INSTR(Push) MOTH_BEGIN_INSTR(CallValue) diff --git a/src/qml/qml/v4/qv4codegen.cpp b/src/qml/qml/v4/qv4codegen.cpp index 804643687e..fb5eb128df 100644 --- a/src/qml/qml/v4/qv4codegen.cpp +++ b/src/qml/qml/v4/qv4codegen.cpp @@ -459,7 +459,6 @@ Codegen::Codegen(QV4::ExecutionContext *context, bool strict) , _scopeAndFinally(0) , _context(context) , _strictMode(strict) - , _debugger(context->engine->debugger) , _errorHandler(0) { } @@ -478,7 +477,6 @@ Codegen::Codegen(ErrorHandler *errorHandler, bool strictMode) , _scopeAndFinally(0) , _context(0) , _strictMode(strictMode) - , _debugger(0) , _errorHandler(errorHandler) { } @@ -501,13 +499,6 @@ V4IR::Function *Codegen::operator()(const QString &fileName, V4IR::Function *globalCode = defineFunction(QStringLiteral("%entry"), node, 0, node->elements, mode, inheritedLocals); - if (_debugger) { - if (node->elements->element) { - SourceLocation loc = node->elements->element->firstSourceLocation(); - _debugger->setSourceLocation(globalCode, loc.startLine, loc.startColumn); - } - } - qDeleteAll(_envMap); _envMap.clear(); @@ -530,8 +521,6 @@ V4IR::Function *Codegen::operator()(const QString &fileName, scan.leaveEnvironment(); V4IR::Function *function = defineFunction(ast->name.toString(), ast, ast->formals, ast->body ? ast->body->elements : 0); - if (_debugger) - _debugger->setSourceLocation(function, ast->functionToken.startLine, ast->functionToken.startColumn); qDeleteAll(_envMap); _envMap.clear(); @@ -1410,8 +1399,6 @@ bool Codegen::visit(FieldMemberExpression *ast) bool Codegen::visit(FunctionExpression *ast) { V4IR::Function *function = defineFunction(ast->name.toString(), ast, ast->formals, ast->body ? ast->body->elements : 0); - if (_debugger) - _debugger->setSourceLocation(function, ast->functionToken.startLine, ast->functionToken.startColumn); _expr.code = _block->CLOSURE(function); return false; } @@ -1546,8 +1533,6 @@ bool Codegen::visit(ObjectLiteral *ast) } else if (PropertyGetterSetter *gs = AST::cast<AST::PropertyGetterSetter *>(it->assignment)) { QString name = propertyName(gs->name); V4IR::Function *function = defineFunction(name, gs, gs->formals, gs->functionBody ? gs->functionBody->elements : 0); - if (_debugger) - _debugger->setSourceLocation(function, gs->getSetToken.startLine, gs->getSetToken.startColumn); ObjectPropertyValue &v = valueMap[name]; if (v.value || (gs->type == PropertyGetterSetter::Getter && v.getter) || @@ -1825,8 +1810,6 @@ V4IR::Function *Codegen::defineFunction(const QString &name, AST::Node *ast, V4IR::Function *function = _module->newFunction(name, _function); function->sourceFile = _fileName; - if (_debugger) - _debugger->addFunction(function); V4IR::BasicBlock *entryBlock = function->newBasicBlock(groupStartBlock()); V4IR::BasicBlock *exitBlock = function->newBasicBlock(groupStartBlock(), V4IR::Function::DontInsertBlock); V4IR::BasicBlock *throwBlock = function->newBasicBlock(groupStartBlock()); @@ -1899,8 +1882,6 @@ V4IR::Function *Codegen::defineFunction(const QString &name, AST::Node *ast, if (member.function) { V4IR::Function *function = defineFunction(member.function->name.toString(), member.function, member.function->formals, member.function->body ? member.function->body->elements : 0); - if (_debugger) - _debugger->setSourceLocation(function, member.function->functionToken.startLine, member.function->functionToken.startColumn); if (! _env->parent) { move(_block->NAME(member.function->name.toString(), member.function->identifierToken.startLine, member.function->identifierToken.startColumn), _block->CLOSURE(function)); diff --git a/src/qml/qml/v4/qv4codegen_p.h b/src/qml/qml/v4/qv4codegen_p.h index 484fa298b4..fe00d87852 100644 --- a/src/qml/qml/v4/qv4codegen_p.h +++ b/src/qml/qml/v4/qv4codegen_p.h @@ -59,9 +59,6 @@ namespace QQmlJS { namespace AST { class UiParameterList; } -namespace Debugging { -class Debugger; -} // namespace Debugging @@ -442,7 +439,6 @@ private: QHash<AST::FunctionExpression *, int> _functionMap; QV4::ExecutionContext *_context; bool _strictMode; - Debugging::Debugger *_debugger; ErrorHandler *_errorHandler; class ScanFunctions; diff --git a/src/qml/qml/v4/qv4debugging.cpp b/src/qml/qml/v4/qv4debugging.cpp index 5a5503214c..624390d8f2 100644 --- a/src/qml/qml/v4/qv4debugging.cpp +++ b/src/qml/qml/v4/qv4debugging.cpp @@ -42,188 +42,163 @@ #include "qv4debugging_p.h" #include "qv4object_p.h" #include "qv4functionobject_p.h" +#include "qv4function_p.h" +#include "moth/qv4instr_moth_p.h" #include <iostream> -#define LOW_LEVEL_DEBUGGING_HELPERS +using namespace QV4; +using namespace QV4::Debugging; -using namespace QQmlJS; -using namespace QQmlJS::Debugging; - -FunctionState::FunctionState(QV4::ExecutionContext *context) - : _context(context) +Debugger::Debugger(QV4::ExecutionEngine *engine) + : _engine(engine) + , m_agent(0) + , m_state(Running) + , m_pauseRequested(false) + , m_currentInstructionPointer(0) { - if (debugger()) - debugger()->enterFunction(this); + qMetaTypeId<Debugger*>(); } -FunctionState::~FunctionState() +Debugger::~Debugger() { - if (debugger()) - debugger()->leaveFunction(this); + detachFromAgent(); } -QV4::Value *FunctionState::argument(unsigned idx) +void Debugger::attachToAgent(DebuggerAgent *agent) { - QV4::CallContext *c = _context->asCallContext(); - if (!c || idx >= c->argumentCount) - return 0; - return c->arguments + idx; + Q_ASSERT(!m_agent); + m_agent = agent; } -QV4::Value *FunctionState::local(unsigned idx) +void Debugger::detachFromAgent() { - QV4::CallContext *c = _context->asCallContext(); - if (c && idx < c->variableCount()) - return c->locals + idx; - return 0; + DebuggerAgent *agent = 0; + { + QMutexLocker locker(&m_lock); + agent = m_agent; + m_agent = 0; + } + if (agent) + agent->removeDebugger(this); } -#ifdef LOW_LEVEL_DEBUGGING_HELPERS -Debugger *globalInstance = 0; - -void printStackTrace() +void Debugger::pause() { - if (globalInstance) - globalInstance->printStackTrace(); - else - std::cerr << "No debugger." << std::endl; + QMutexLocker locker(&m_lock); + if (m_state == Paused) + return; + m_pauseRequested = true; } -#endif // DO_TRACE_INSTR -Debugger::Debugger(QV4::ExecutionEngine *engine) - : _engine(engine) +void Debugger::resume() { -#ifdef LOW_LEVEL_DEBUGGING_HELPERS - globalInstance = this; -#endif // DO_TRACE_INSTR + QMutexLocker locker(&m_lock); + Q_ASSERT(m_state == Paused); + m_runningCondition.wakeAll(); } -Debugger::~Debugger() +void Debugger::addBreakPoint(const QString &fileName, int lineNumber) { -#ifdef LOW_LEVEL_DEBUGGING_HELPERS - globalInstance = 0; -#endif // DO_TRACE_INSTR - - qDeleteAll(_functionInfo.values()); + QMutexLocker locker(&m_lock); + if (!m_pendingBreakPointsToRemove.remove(fileName, lineNumber)) + m_pendingBreakPointsToAdd.add(fileName, lineNumber); + m_havePendingBreakPoints = !m_pendingBreakPointsToAdd.isEmpty() || !m_pendingBreakPointsToRemove.isEmpty(); } -void Debugger::addFunction(V4IR::Function *function) +void Debugger::removeBreakPoint(const QString &fileName, int lineNumber) { - _functionInfo.insert(function, new FunctionDebugInfo(function)); + QMutexLocker locker(&m_lock); + if (!m_pendingBreakPointsToAdd.remove(fileName, lineNumber)) + m_pendingBreakPointsToRemove.add(fileName, lineNumber); + m_havePendingBreakPoints = !m_pendingBreakPointsToAdd.isEmpty() || !m_pendingBreakPointsToRemove.isEmpty(); } -void Debugger::setSourceLocation(V4IR::Function *function, unsigned line, unsigned column) +Debugger::ExecutionState Debugger::currentExecutionState(const uchar *code) const { - _functionInfo[function]->setSourceLocation(line, column); -} + if (!code) + code = m_currentInstructionPointer; + // ### 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; + } -void Debugger::mapFunction(QV4::Function *vmf, V4IR::Function *irf) -{ - _vmToIr.insert(vmf, irf); -} + state.function = function; + state.fileName = function->sourceFile; -FunctionDebugInfo *Debugger::debugInfo(QV4::FunctionObject *function) const -{ - if (!function) - return 0; + qptrdiff relativeProgramCounter = code - function->codeData; + state.lineNumber = function->lineNumberForProgramCounter(relativeProgramCounter); - if (function->function) - return _functionInfo[irFunction(function->function)]; - else - return 0; + return state; } -QString Debugger::name(QV4::FunctionObject *function) const +void Debugger::setPendingBreakpoints(Function *function) { - if (FunctionDebugInfo *i = debugInfo(function)) - return i->name; - - return QString(); + m_pendingBreakPointsToAddToFutureCode.applyToFunction(function, /*removeBreakPoints*/ false); } -void Debugger::aboutToCall(QV4::FunctionObject *function, QV4::ExecutionContext *context) +void Debugger::maybeBreakAtInstruction(const uchar *code, bool breakPointHit) { - _callStack.append(CallInfo(context, function)); -} + QMutexLocker locker(&m_lock); + m_currentInstructionPointer = code; -void Debugger::justLeft(QV4::ExecutionContext *context) -{ - int idx = callIndex(context); - if (idx < 0) - qDebug() << "Oops, leaving a function that was not registered...?"; - else - _callStack.resize(idx); -} + // Do debugger internal work + if (m_havePendingBreakPoints) { -void Debugger::enterFunction(FunctionState *state) -{ - _callStack[callIndex(state->context())].state = state; - -#ifdef DO_TRACE_INSTR - QString n = name(_callStack[callIndex(state->context())].function); - std::cerr << "*** Entering \"" << qPrintable(n) << "\" with " << state->context()->argumentCount << " args" << std::endl; -// for (unsigned i = 0; i < state->context()->variableEnvironment->argumentCount; ++i) -// std::cerr << " " << i << ": " << currentArg(i) << std::endl; -#endif // DO_TRACE_INSTR -} + if (breakPointHit) { + ExecutionState state = currentExecutionState(); + breakPointHit = !m_pendingBreakPointsToRemove.contains(state.fileName, state.lineNumber); + } -void Debugger::leaveFunction(FunctionState *state) -{ - _callStack[callIndex(state->context())].state = 0; -} - -void Debugger::aboutToThrow(const QV4::Value &value) -{ - qDebug() << "*** We are about to throw...:" << value.toString(currentState()->context())->toQString(); -} + applyPendingBreakPoints(); + } -FunctionState *Debugger::currentState() const -{ - if (_callStack.isEmpty()) - return 0; - else - return _callStack.last().state; -} + // Serve debugging requests from the agent + if (m_pauseRequested) { + m_pauseRequested = false; + pauseAndWait(); + } else if (breakPointHit) + pauseAndWait(); -const char *Debugger::currentArg(unsigned idx) const -{ - FunctionState *state = currentState(); - return qPrintable(state->argument(idx)->toString(state->context())->toQString()); + if (!m_pendingBreakPointsToAdd.isEmpty() || !m_pendingBreakPointsToRemove.isEmpty()) + applyPendingBreakPoints(); } -const char *Debugger::currentLocal(unsigned idx) const +void Debugger::aboutToThrow(const QV4::Value &value) { - FunctionState *state = currentState(); - return qPrintable(state->local(idx)->toString(state->context())->toQString()); + qDebug() << "*** We are about to throw..."; } -const char *Debugger::currentTemp(unsigned idx) const +void Debugger::pauseAndWait() { - FunctionState *state = currentState(); - return qPrintable(state->temp(idx)->toString(state->context())->toQString()); + m_state = Paused; + QMetaObject::invokeMethod(m_agent, "debuggerPaused", Qt::QueuedConnection, Q_ARG(QV4::Debugging::Debugger*, this)); + m_runningCondition.wait(&m_lock); + m_state = Running; } -void Debugger::printStackTrace() const +void Debugger::applyPendingBreakPoints() { - for (int i = _callStack.size() - 1; i >=0; --i) { - QString n = name(_callStack[i].function); - std::cerr << "\tframe #" << i << ": " << qPrintable(n) << std::endl; + foreach (Function *function, _engine->functions) { + m_pendingBreakPointsToAdd.applyToFunction(function, /*removeBreakPoints*/false); + m_pendingBreakPointsToRemove.applyToFunction(function, /*removeBreakPoints*/true); } -} -int Debugger::callIndex(QV4::ExecutionContext *context) -{ - for (int idx = _callStack.size() - 1; idx >= 0; --idx) { - if (_callStack[idx].context == context) - return idx; + for (BreakPoints::ConstIterator it = m_pendingBreakPointsToAdd.constBegin(), + end = m_pendingBreakPointsToAdd.constEnd(); it != end; ++it) { + foreach (int lineNumber, it.value()) + m_pendingBreakPointsToAddToFutureCode.add(it.key(), lineNumber); } - return -1; -} - -V4IR::Function *Debugger::irFunction(QV4::Function *vmf) const -{ - return _vmToIr[vmf]; + m_pendingBreakPointsToAdd.clear(); + m_pendingBreakPointsToRemove.clear(); + m_havePendingBreakPoints = false; } static void realDumpValue(QV4::Value v, QV4::ExecutionContext *ctx, std::string prefix) @@ -306,3 +281,92 @@ void dumpValue(QV4::Value v, QV4::ExecutionContext *ctx) { realDumpValue(v, ctx, std::string("")); } + + +void DebuggerAgent::addDebugger(Debugger *debugger) +{ + Q_ASSERT(!m_debuggers.contains(debugger)); + m_debuggers << debugger; + debugger->attachToAgent(this); +} + +void DebuggerAgent::removeDebugger(Debugger *debugger) +{ + m_debuggers.removeAll(debugger); + debugger->detachFromAgent(); +} + +void DebuggerAgent::pause(Debugger *debugger) +{ + debugger->pause(); +} + +void DebuggerAgent::addBreakPoint(Debugger *debugger, const QString &fileName, int lineNumber) +{ + debugger->addBreakPoint(fileName, lineNumber); +} + +void DebuggerAgent::removeBreakPoint(Debugger *debugger, const QString &fileName, int lineNumber) +{ + debugger->removeBreakPoint(fileName, lineNumber); +} + +DebuggerAgent::~DebuggerAgent() +{ + Q_ASSERT(m_debuggers.isEmpty()); +} + +void Debugger::BreakPoints::add(const QString &fileName, int lineNumber) +{ + QList<int> &lines = (*this)[fileName]; + if (!lines.contains(lineNumber)) { + lines.append(lineNumber); + qSort(lines); + } +} + +bool Debugger::BreakPoints::remove(const QString &fileName, int lineNumber) +{ + Iterator breakPoints = find(fileName); + if (breakPoints == constEnd()) + return false; + return breakPoints->removeAll(lineNumber) > 0; +} + +bool Debugger::BreakPoints::contains(const QString &fileName, int lineNumber) const +{ + ConstIterator breakPoints = find(fileName); + if (breakPoints == constEnd()) + return false; + return breakPoints->contains(lineNumber); +} + +void Debugger::BreakPoints::applyToFunction(Function *function, bool removeBreakPoints) +{ + Iterator breakPointsForFile = find(function->sourceFile); + if (breakPointsForFile == end()) + return; + + QList<int>::Iterator breakPoint = breakPointsForFile->begin(); + while (breakPoint != breakPointsForFile->end()) { + bool breakPointFound = false; + for (QVector<LineNumberMapping>::ConstIterator mapping = function->lineNumberMappings.constBegin(), + end = function->lineNumberMappings.constEnd(); mapping != end; ++mapping) { + if (mapping->lineNumber == *breakPoint) { + uchar *codePtr = const_cast<uchar *>(function->codeData) + mapping->codeOffset; + QQmlJS::Moth::Instr *instruction = reinterpret_cast<QQmlJS::Moth::Instr*>(codePtr); + instruction->common.breakPoint = !removeBreakPoints; + // Continue setting the next break point. + breakPointFound = true; + break; + } + } + if (breakPointFound) + breakPoint = breakPointsForFile->erase(breakPoint); + else + ++breakPoint; + } + + if (breakPointsForFile->isEmpty()) + erase(breakPointsForFile); +} diff --git a/src/qml/qml/v4/qv4debugging_p.h b/src/qml/qml/v4/qv4debugging_p.h index 51bac14927..4a273be732 100644 --- a/src/qml/qml/v4/qv4debugging_p.h +++ b/src/qml/qml/v4/qv4debugging_p.h @@ -45,113 +45,117 @@ #include "qv4global_p.h" #include "qv4engine_p.h" #include "qv4context_p.h" +#include "qv4jsir_p.h" #include <QHash> +#include <QThread> +#include <QMutex> +#include <QWaitCondition> QT_BEGIN_NAMESPACE -namespace QQmlJS { +namespace QV4 { -namespace V4IR { -struct BasicBlock; struct Function; -} // namespace IR namespace Debugging { -class Debugger; +class DebuggerAgent; -struct FunctionDebugInfo { // TODO: use opaque d-pointers here - QString name; - unsigned startLine, startColumn; +class Q_QML_EXPORT Debugger +{ +public: + enum State { + Running, + Paused + }; - FunctionDebugInfo(V4IR::Function *function): - startLine(0), startColumn(0) - { - if (function->name) - name = *function->name; - } + Debugger(ExecutionEngine *_engine); + ~Debugger(); - void setSourceLocation(unsigned line, unsigned column) - { startLine = line; startColumn = column; } -}; + void attachToAgent(DebuggerAgent *agent); + void detachFromAgent(); -class FunctionState -{ -public: - FunctionState(QV4::ExecutionContext *context); - virtual ~FunctionState(); + void pause(); + void resume(); + + State state() const { return m_state; } + + void addBreakPoint(const QString &fileName, int lineNumber); + void removeBreakPoint(const QString &fileName, int lineNumber); + + struct ExecutionState + { + ExecutionState() : lineNumber(-1), function(0) {} + QString fileName; + int lineNumber; + Function *function; + }; - virtual QV4::Value *argument(unsigned idx); - virtual QV4::Value *local(unsigned idx); - virtual QV4::Value *temp(unsigned idx) = 0; + ExecutionState currentExecutionState(const uchar *code = 0) const; - QV4::ExecutionContext *context() const - { return _context; } + bool pauseAtNextOpportunity() const { + return m_pauseRequested || m_havePendingBreakPoints; + } + void setPendingBreakpoints(Function *function); - Debugger *debugger() const - { return _context->engine->debugger; } +public: // compile-time interface + void maybeBreakAtInstruction(const uchar *code, bool breakPointHit); + +public: // execution hooks + void aboutToThrow(const Value &value); private: - QV4::ExecutionContext *_context; -}; + // requires lock to be held + void pauseAndWait(); -struct CallInfo -{ - QV4::ExecutionContext *context; - QV4::FunctionObject *function; - FunctionState *state; - - CallInfo(QV4::ExecutionContext *context = 0, QV4::FunctionObject *function = 0, FunctionState *state = 0) - : context(context) - , function(function) - , state(state) - {} + void applyPendingBreakPoints(); + + struct BreakPoints : public QHash<QString, QList<int> > + { + void add(const QString &fileName, int lineNumber); + bool remove(const QString &fileName, int lineNumber); + bool contains(const QString &fileName, int lineNumber) const; + void applyToFunction(Function *function, bool removeBreakPoints); + }; + + QV4::ExecutionEngine *_engine; + DebuggerAgent *m_agent; + QMutex m_lock; + QWaitCondition m_runningCondition; + State m_state; + bool m_pauseRequested; + bool m_havePendingBreakPoints; + BreakPoints m_pendingBreakPointsToAdd; + BreakPoints m_pendingBreakPointsToAddToFutureCode; + BreakPoints m_pendingBreakPointsToRemove; + const uchar *m_currentInstructionPointer; }; -class Q_QML_EXPORT Debugger +class Q_QML_EXPORT DebuggerAgent : public QObject { + Q_OBJECT public: - Debugger(QV4::ExecutionEngine *_engine); - ~Debugger(); + ~DebuggerAgent(); -public: // compile-time interface - void addFunction(V4IR::Function *function); - void setSourceLocation(V4IR::Function *function, unsigned line, unsigned column); - void mapFunction(QV4::Function *vmf, V4IR::Function *irf); + void addDebugger(Debugger *debugger); + void removeDebugger(Debugger *debugger); -public: // run-time querying interface - FunctionDebugInfo *debugInfo(QV4::FunctionObject *function) const; - QString name(QV4::FunctionObject *function) const; + void pause(Debugger *debugger); + void addBreakPoint(Debugger *debugger, const QString &fileName, int lineNumber); + void removeBreakPoint(Debugger *debugger, const QString &fileName, int lineNumber); -public: // execution hooks - void aboutToCall(QV4::FunctionObject *function, QV4::ExecutionContext *context); - void justLeft(QV4::ExecutionContext *context); - void enterFunction(FunctionState *state); - void leaveFunction(FunctionState *state); - void aboutToThrow(const QV4::Value &value); - -public: // debugging hooks - FunctionState *currentState() const; - const char *currentArg(unsigned idx) const; - const char *currentLocal(unsigned idx) const; - const char *currentTemp(unsigned idx) const; - void printStackTrace() const; + Q_INVOKABLE virtual void debuggerPaused(QV4::Debugging::Debugger *debugger) = 0; -private: - int callIndex(QV4::ExecutionContext *context); - V4IR::Function *irFunction(QV4::Function *vmf) const; - -private: // TODO: use opaque d-pointers here - QV4::ExecutionEngine *_engine; - QHash<V4IR::Function *, FunctionDebugInfo *> _functionInfo; - QHash<QV4::Function *, V4IR::Function *> _vmToIr; - QVector<CallInfo> _callStack; +protected: + QList<Debugger *> m_debuggers; }; } // namespace Debugging -} // namespace QQmlJS +} // namespace QV4 QT_END_NAMESPACE +Q_DECLARE_METATYPE(QV4::Debugging::Debugger*) + #endif // DEBUGGING_H diff --git a/src/qml/qml/v4/qv4engine.cpp b/src/qml/qml/v4/qv4engine.cpp index 56c711ceb7..0b7c850aa6 100644 --- a/src/qml/qml/v4/qv4engine.cpp +++ b/src/qml/qml/v4/qv4engine.cpp @@ -68,11 +68,11 @@ #include "qv4stacktrace_p.h" #ifdef V4_ENABLE_JIT -# include "qv4isel_masm_p.h" -#else // !V4_ENABLE_JIT -# include "qv4isel_moth_p.h" +#include "qv4isel_masm_p.h" #endif // V4_ENABLE_JIT +#include "qv4isel_moth_p.h" + QT_BEGIN_NAMESPACE using namespace QV4; @@ -270,6 +270,7 @@ ExecutionEngine::ExecutionEngine(QQmlJS::EvalISelFactory *factory) ExecutionEngine::~ExecutionEngine() { + delete debugger; delete m_multiplyWrappedQObjects; m_multiplyWrappedQObjects = 0; delete memoryManager; @@ -284,6 +285,13 @@ ExecutionEngine::~ExecutionEngine() delete executableAllocator; } +void ExecutionEngine::enableDebugger() +{ + Q_ASSERT(!debugger); + debugger = new Debugging::Debugger(this); + iselFactory.reset(new QQmlJS::Moth::ISelFactory); +} + void ExecutionEngine::initRootContext() { rootContext = static_cast<GlobalContext *>(memoryManager->allocContext(sizeof(GlobalContext))); diff --git a/src/qml/qml/v4/qv4engine_p.h b/src/qml/qml/v4/qv4engine_p.h index 842fb44ecb..20fbf03ae2 100644 --- a/src/qml/qml/v4/qv4engine_p.h +++ b/src/qml/qml/v4/qv4engine_p.h @@ -56,7 +56,7 @@ QT_BEGIN_NAMESPACE class QV8Engine; -namespace QQmlJS { +namespace QV4 { namespace Debugging { class Debugger; } // namespace Debugging @@ -121,7 +121,7 @@ struct Q_QML_EXPORT ExecutionEngine IdentifierTable *identifierTable; - QQmlJS::Debugging::Debugger *debugger; + QV4::Debugging::Debugger *debugger; Object *globalObject; @@ -227,6 +227,8 @@ struct Q_QML_EXPORT ExecutionEngine ExecutionEngine(QQmlJS::EvalISelFactory *iselFactory = 0); ~ExecutionEngine(); + void enableDebugger(); + WithContext *newWithContext(Object *with); CatchContext *newCatchContext(String* exceptionVarName, const QV4::Value &exceptionValue); CallContext *newCallContext(FunctionObject *f, const QV4::Value &thisObject, QV4::Value *args, int argc); diff --git a/src/qml/qml/v4/qv4isel_p.cpp b/src/qml/qml/v4/qv4isel_p.cpp index 99c363bd36..ca8d249f9f 100644 --- a/src/qml/qml/v4/qv4isel_p.cpp +++ b/src/qml/qml/v4/qv4isel_p.cpp @@ -103,9 +103,6 @@ QV4::Function *EvalInstructionSelection::createFunctionMapping(QV4::Function *ou foreach (V4IR::Function *function, irFunction->nestedFunctions) createFunctionMapping(vmFunction, function); - if (_engine->debugger) - _engine->debugger->mapFunction(vmFunction, irFunction); - return vmFunction; } diff --git a/src/qml/qml/v4/qv4script.cpp b/src/qml/qml/v4/qv4script.cpp index 1a6098b2ce..3de218a451 100644 --- a/src/qml/qml/v4/qv4script.cpp +++ b/src/qml/qml/v4/qv4script.cpp @@ -192,9 +192,6 @@ Value Script::run() QV4::ExecutionEngine *engine = scope->engine; - if (engine->debugger) - engine->debugger->aboutToCall(0, scope); - if (qml.isEmpty()) { TemporaryAssignment<Function*> savedGlobalCode(engine->globalCode, vmFunction); @@ -204,9 +201,6 @@ Value Script::run() scope->strictMode = vmFunction->isStrict; scope->lookups = vmFunction->lookups; - if (engine->debugger) - engine->debugger->aboutToCall(0, scope); - QV4::Value result; try { result = vmFunction->code(scope, vmFunction->codeData); @@ -216,8 +210,6 @@ Value Script::run() throw; } - if (engine->debugger) - engine->debugger->justLeft(scope); return result; } else { diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro index 371d47ba32..18065f1a24 100644 --- a/tests/auto/qml/qml.pro +++ b/tests/auto/qml/qml.pro @@ -54,7 +54,8 @@ PRIVATETESTS += \ qqmlbundle \ qrcqml \ qqmltimer \ - qqmlinstantiator + qqmlinstantiator \ + qv4debugger qtHaveModule(widgets) { PUBLICTESTS += \ diff --git a/tests/auto/qml/qv4debugger/qv4debugger.pro b/tests/auto/qml/qv4debugger/qv4debugger.pro new file mode 100644 index 0000000000..2a318955f3 --- /dev/null +++ b/tests/auto/qml/qv4debugger/qv4debugger.pro @@ -0,0 +1,7 @@ +CONFIG += testcase +TARGET = tst_qv4debugger +macx:CONFIG -= app_bundle + +SOURCES += tst_qv4debugger.cpp + +QT += core-private gui-private qml-private network testlib diff --git a/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp b/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp new file mode 100644 index 0000000000..c6ecd1938a --- /dev/null +++ b/tests/auto/qml/qv4debugger/tst_qv4debugger.cpp @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtTest/QtTest> + +#include <QJSEngine> +#include <private/qv4engine_p.h> +#include <private/qv4debugging_p.h> +#include <private/qv8engine_p.h> + +static bool waitForSignal(QObject* obj, const char* signal, int timeout = 10000) +{ + QEventLoop loop; + QObject::connect(obj, signal, &loop, SLOT(quit())); + QTimer timer; + QSignalSpy timeoutSpy(&timer, SIGNAL(timeout())); + if (timeout > 0) { + QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + timer.setSingleShot(true); + timer.start(timeout); + } + loop.exec(); + return timeoutSpy.isEmpty(); +} + +class TestEngine : public QJSEngine +{ + Q_OBJECT +public: + TestEngine() + { + qMetaTypeId<InjectedFunction>(); + } + + Q_INVOKABLE void evaluate(const QString &script, const QString &fileName, int lineNumber = 1) + { + QJSEngine::evaluate(script, fileName, lineNumber); + emit evaluateFinished(); + } + + QV4::ExecutionEngine *v4Engine() { return QV8Engine::getV4(this); } + + typedef QV4::Value (*InjectedFunction)(QV4::SimpleCallContext*); + + Q_INVOKABLE void injectFunction(const QString &functionName, TestEngine::InjectedFunction injectedFunction) + { + QV4::ExecutionEngine *v4 = v4Engine(); + + QV4::String *name = v4->newString(functionName); + QV4::Value function = QV4::Value::fromObject(v4->newBuiltinFunction(v4->rootContext, name, injectedFunction)); + v4->globalObject->put(name, function); + } + +signals: + void evaluateFinished(); +}; + +Q_DECLARE_METATYPE(TestEngine::InjectedFunction) + +class TestAgent : public QV4::Debugging::DebuggerAgent +{ + Q_OBJECT +public: + TestAgent() + : m_wasPaused(false) + { + } + + virtual void debuggerPaused(QV4::Debugging::Debugger *debugger) + { + Q_ASSERT(m_debuggers.count() == 1 && m_debuggers.first() == debugger); + m_wasPaused = true; + m_statesWhenPaused << debugger->currentExecutionState(); + + foreach (const TestBreakPoint &bp, m_breakPointsToAddWhenPaused) + debugger->addBreakPoint(bp.fileName, bp.lineNumber); + m_breakPointsToAddWhenPaused.clear(); + + debugger->resume(); + } + + int debuggerCount() const { return m_debuggers.count(); } + + struct TestBreakPoint + { + TestBreakPoint() : lineNumber(-1) {} + TestBreakPoint(const QString &fileName, int lineNumber) + : fileName(fileName), lineNumber(lineNumber) {} + QString fileName; + int lineNumber; + }; + + bool m_wasPaused; + QList<QV4::Debugging::Debugger::ExecutionState> m_statesWhenPaused; + QList<TestBreakPoint> m_breakPointsToAddWhenPaused; +}; + +class tst_qv4debugger : public QObject +{ + Q_OBJECT +private slots: + void init(); + void cleanup(); + + void breakAnywhere(); + void pendingBreakpoint(); + void liveBreakPoint(); + void removePendingBreakPoint(); + void addBreakPointWhilePaused(); + void removeBreakPointForNextInstruction(); + +private: + void evaluateJavaScript(const QString &script, const QString &fileName, int lineNumber = 1) + { + QMetaObject::invokeMethod(m_engine, "evaluate", Qt::QueuedConnection, + Q_ARG(QString, script), Q_ARG(QString, fileName), + Q_ARG(int, lineNumber)); + waitForSignal(m_engine, SIGNAL(evaluateFinished()), /*timeout*/0); + } + + TestEngine *m_engine; + QV4::ExecutionEngine *m_v4; + TestAgent *m_debuggerAgent; + QThread *m_javaScriptThread; +}; + +void tst_qv4debugger::init() +{ + m_javaScriptThread = new QThread; + m_engine = new TestEngine; + m_v4 = m_engine->v4Engine(); + m_v4->enableDebugger(); + m_engine->moveToThread(m_javaScriptThread); + m_javaScriptThread->start(); + m_debuggerAgent = new TestAgent; + m_debuggerAgent->addDebugger(m_v4->debugger); +} + +void tst_qv4debugger::cleanup() +{ + m_javaScriptThread->exit(); + waitForSignal(m_javaScriptThread, SIGNAL(finished()), /*timeout*/ 0); + delete m_engine; + delete m_javaScriptThread; + m_engine = 0; + m_v4 = 0; + QCOMPARE(m_debuggerAgent->debuggerCount(), 0); + delete m_debuggerAgent; + m_debuggerAgent = 0; +} + +void tst_qv4debugger::breakAnywhere() +{ + QString script = + "var i = 42;\n" + "var j = i + 1\n" + "var k = i\n"; + m_debuggerAgent->pause(m_v4->debugger); + evaluateJavaScript(script, "testFile"); + QVERIFY(m_debuggerAgent->m_wasPaused); +} + +void tst_qv4debugger::pendingBreakpoint() +{ + QString script = + "var i = 42;\n" + "var j = i + 1\n" + "var k = i\n"; + m_debuggerAgent->addBreakPoint(m_v4->debugger, "testfile", 2); + evaluateJavaScript(script, "testfile"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 1); + QV4::Debugging::Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.first(); + QCOMPARE(state.fileName, QString("testfile")); + QCOMPARE(state.lineNumber, 2); +} + +void tst_qv4debugger::liveBreakPoint() +{ + QString script = + "var i = 42;\n" + "var j = i + 1\n" + "var k = i\n"; + m_debuggerAgent->m_breakPointsToAddWhenPaused << TestAgent::TestBreakPoint("liveBreakPoint", 3); + m_debuggerAgent->pause(m_v4->debugger); + evaluateJavaScript(script, "liveBreakPoint"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 2); + QV4::Debugging::Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.at(1); + QCOMPARE(state.fileName, QString("liveBreakPoint")); + QCOMPARE(state.lineNumber, 3); +} + +void tst_qv4debugger::removePendingBreakPoint() +{ + QString script = + "var i = 42;\n" + "var j = i + 1\n" + "var k = i\n"; + m_debuggerAgent->addBreakPoint(m_v4->debugger, "removePendingBreakPoint", 2); + m_debuggerAgent->removeBreakPoint(m_v4->debugger, "removePendingBreakPoint", 2); + evaluateJavaScript(script, "removePendingBreakPoint"); + QVERIFY(!m_debuggerAgent->m_wasPaused); +} + +void tst_qv4debugger::addBreakPointWhilePaused() +{ + QString script = + "var i = 42;\n" + "var j = i + 1\n" + "var k = i\n"; + m_debuggerAgent->addBreakPoint(m_v4->debugger, "addBreakPointWhilePaused", 1); + m_debuggerAgent->m_breakPointsToAddWhenPaused << TestAgent::TestBreakPoint("addBreakPointWhilePaused", 2); + evaluateJavaScript(script, "addBreakPointWhilePaused"); + QVERIFY(m_debuggerAgent->m_wasPaused); + QCOMPARE(m_debuggerAgent->m_statesWhenPaused.count(), 2); + + QV4::Debugging::Debugger::ExecutionState state = m_debuggerAgent->m_statesWhenPaused.at(0); + QCOMPARE(state.fileName, QString("addBreakPointWhilePaused")); + QCOMPARE(state.lineNumber, 1); + + state = m_debuggerAgent->m_statesWhenPaused.at(1); + QCOMPARE(state.fileName, QString("addBreakPointWhilePaused")); + QCOMPARE(state.lineNumber, 2); +} + +static QV4::Value someCall(QV4::SimpleCallContext *ctx) +{ + ctx->engine->debugger->removeBreakPoint("removeBreakPointForNextInstruction", 2); + return QV4::Value::undefinedValue(); +} + +void tst_qv4debugger::removeBreakPointForNextInstruction() +{ + QString script = + "someCall();\n" + "var i = 42;"; + + QMetaObject::invokeMethod(m_engine, "injectFunction", Qt::BlockingQueuedConnection, + Q_ARG(QString, "someCall"), Q_ARG(TestEngine::InjectedFunction, someCall)); + + m_debuggerAgent->addBreakPoint(m_v4->debugger, "removeBreakPointForNextInstruction", 2); + + evaluateJavaScript(script, "removeBreakPointForNextInstruction"); + QVERIFY(!m_debuggerAgent->m_wasPaused); +} + +QTEST_MAIN(tst_qv4debugger) + +#include "tst_qv4debugger.moc" diff --git a/tools/v4/main.cpp b/tools/v4/main.cpp index cc1dc71b60..558055e677 100644 --- a/tools/v4/main.cpp +++ b/tools/v4/main.cpp @@ -43,7 +43,6 @@ # include "private/qv4_llvm_p.h" #endif // QMLJS_WITH_LLVM -#include "private/qv4debugging_p.h" #include "private/qv4object_p.h" #include "private/qv4runtime_p.h" #include "private/qv4functionobject_p.h" @@ -287,17 +286,9 @@ int main(int argc, char *argv[]) #ifdef QMLJS_WITH_LLVM QQmlJS::LLVMOutputType fileType = QQmlJS::LLVMOutputObject; #endif // QMLJS_WITH_LLVM - bool enableDebugging = false; bool runAsQml = false; if (!args.isEmpty()) { - if (args.first() == QLatin1String("-d") || args.first() == QLatin1String("--debug")) { - enableDebugging = true; - args.removeFirst(); - } - } - - if (!args.isEmpty()) { if (args.first() == QLatin1String("--jit")) { mode = use_masm; args.removeFirst(); @@ -375,11 +366,6 @@ int main(int argc, char *argv[]) QV4::ExecutionEngine vm(iSelFactory); - QScopedPointer<QQmlJS::Debugging::Debugger> debugger; - if (enableDebugging) - debugger.reset(new QQmlJS::Debugging::Debugger(&vm)); - vm.debugger = debugger.data(); - QV4::ExecutionContext *ctx = vm.rootContext; QV4::Object *globalObject = vm.globalObject; |