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>
This commit is contained in:
parent
993bc84f49
commit
07860794da
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 QQmlJS;
|
||||
using namespace QQmlJS::Debugging;
|
||||
|
||||
FunctionState::FunctionState(QV4::ExecutionContext *context)
|
||||
: _context(context)
|
||||
{
|
||||
if (debugger())
|
||||
debugger()->enterFunction(this);
|
||||
}
|
||||
|
||||
FunctionState::~FunctionState()
|
||||
{
|
||||
if (debugger())
|
||||
debugger()->leaveFunction(this);
|
||||
}
|
||||
|
||||
QV4::Value *FunctionState::argument(unsigned idx)
|
||||
{
|
||||
QV4::CallContext *c = _context->asCallContext();
|
||||
if (!c || idx >= c->argumentCount)
|
||||
return 0;
|
||||
return c->arguments + idx;
|
||||
}
|
||||
|
||||
QV4::Value *FunctionState::local(unsigned idx)
|
||||
{
|
||||
QV4::CallContext *c = _context->asCallContext();
|
||||
if (c && idx < c->variableCount())
|
||||
return c->locals + idx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef LOW_LEVEL_DEBUGGING_HELPERS
|
||||
Debugger *globalInstance = 0;
|
||||
|
||||
void printStackTrace()
|
||||
{
|
||||
if (globalInstance)
|
||||
globalInstance->printStackTrace();
|
||||
else
|
||||
std::cerr << "No debugger." << std::endl;
|
||||
}
|
||||
#endif // DO_TRACE_INSTR
|
||||
using namespace QV4;
|
||||
using namespace QV4::Debugging;
|
||||
|
||||
Debugger::Debugger(QV4::ExecutionEngine *engine)
|
||||
: _engine(engine)
|
||||
, m_agent(0)
|
||||
, m_state(Running)
|
||||
, m_pauseRequested(false)
|
||||
, m_currentInstructionPointer(0)
|
||||
{
|
||||
#ifdef LOW_LEVEL_DEBUGGING_HELPERS
|
||||
globalInstance = this;
|
||||
#endif // DO_TRACE_INSTR
|
||||
qMetaTypeId<Debugger*>();
|
||||
}
|
||||
|
||||
Debugger::~Debugger()
|
||||
{
|
||||
#ifdef LOW_LEVEL_DEBUGGING_HELPERS
|
||||
globalInstance = 0;
|
||||
#endif // DO_TRACE_INSTR
|
||||
|
||||
qDeleteAll(_functionInfo.values());
|
||||
detachFromAgent();
|
||||
}
|
||||
|
||||
void Debugger::addFunction(V4IR::Function *function)
|
||||
void Debugger::attachToAgent(DebuggerAgent *agent)
|
||||
{
|
||||
_functionInfo.insert(function, new FunctionDebugInfo(function));
|
||||
Q_ASSERT(!m_agent);
|
||||
m_agent = agent;
|
||||
}
|
||||
|
||||
void Debugger::setSourceLocation(V4IR::Function *function, unsigned line, unsigned column)
|
||||
void Debugger::detachFromAgent()
|
||||
{
|
||||
_functionInfo[function]->setSourceLocation(line, column);
|
||||
DebuggerAgent *agent = 0;
|
||||
{
|
||||
QMutexLocker locker(&m_lock);
|
||||
agent = m_agent;
|
||||
m_agent = 0;
|
||||
}
|
||||
if (agent)
|
||||
agent->removeDebugger(this);
|
||||
}
|
||||
|
||||
void Debugger::mapFunction(QV4::Function *vmf, V4IR::Function *irf)
|
||||
void Debugger::pause()
|
||||
{
|
||||
_vmToIr.insert(vmf, irf);
|
||||
QMutexLocker locker(&m_lock);
|
||||
if (m_state == Paused)
|
||||
return;
|
||||
m_pauseRequested = true;
|
||||
}
|
||||
|
||||
FunctionDebugInfo *Debugger::debugInfo(QV4::FunctionObject *function) const
|
||||
void Debugger::resume()
|
||||
{
|
||||
if (!function)
|
||||
return 0;
|
||||
|
||||
if (function->function)
|
||||
return _functionInfo[irFunction(function->function)];
|
||||
else
|
||||
return 0;
|
||||
QMutexLocker locker(&m_lock);
|
||||
Q_ASSERT(m_state == Paused);
|
||||
m_runningCondition.wakeAll();
|
||||
}
|
||||
|
||||
QString Debugger::name(QV4::FunctionObject *function) const
|
||||
void Debugger::addBreakPoint(const QString &fileName, int lineNumber)
|
||||
{
|
||||
if (FunctionDebugInfo *i = debugInfo(function))
|
||||
return i->name;
|
||||
|
||||
return QString();
|
||||
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::aboutToCall(QV4::FunctionObject *function, QV4::ExecutionContext *context)
|
||||
void Debugger::removeBreakPoint(const QString &fileName, int lineNumber)
|
||||
{
|
||||
_callStack.append(CallInfo(context, 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::justLeft(QV4::ExecutionContext *context)
|
||||
Debugger::ExecutionState Debugger::currentExecutionState(const uchar *code) const
|
||||
{
|
||||
int idx = callIndex(context);
|
||||
if (idx < 0)
|
||||
qDebug() << "Oops, leaving a function that was not registered...?";
|
||||
else
|
||||
_callStack.resize(idx);
|
||||
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;
|
||||
}
|
||||
|
||||
state.function = function;
|
||||
state.fileName = function->sourceFile;
|
||||
|
||||
qptrdiff relativeProgramCounter = code - function->codeData;
|
||||
state.lineNumber = function->lineNumberForProgramCounter(relativeProgramCounter);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void Debugger::enterFunction(FunctionState *state)
|
||||
void Debugger::setPendingBreakpoints(Function *function)
|
||||
{
|
||||
_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
|
||||
m_pendingBreakPointsToAddToFutureCode.applyToFunction(function, /*removeBreakPoints*/ false);
|
||||
}
|
||||
|
||||
void Debugger::leaveFunction(FunctionState *state)
|
||||
void Debugger::maybeBreakAtInstruction(const uchar *code, bool breakPointHit)
|
||||
{
|
||||
_callStack[callIndex(state->context())].state = 0;
|
||||
QMutexLocker locker(&m_lock);
|
||||
m_currentInstructionPointer = code;
|
||||
|
||||
// Do debugger internal work
|
||||
if (m_havePendingBreakPoints) {
|
||||
|
||||
if (breakPointHit) {
|
||||
ExecutionState state = currentExecutionState();
|
||||
breakPointHit = !m_pendingBreakPointsToRemove.contains(state.fileName, state.lineNumber);
|
||||
}
|
||||
|
||||
applyPendingBreakPoints();
|
||||
}
|
||||
|
||||
// Serve debugging requests from the agent
|
||||
if (m_pauseRequested) {
|
||||
m_pauseRequested = false;
|
||||
pauseAndWait();
|
||||
} else if (breakPointHit)
|
||||
pauseAndWait();
|
||||
|
||||
if (!m_pendingBreakPointsToAdd.isEmpty() || !m_pendingBreakPointsToRemove.isEmpty())
|
||||
applyPendingBreakPoints();
|
||||
}
|
||||
|
||||
void Debugger::aboutToThrow(const QV4::Value &value)
|
||||
{
|
||||
qDebug() << "*** We are about to throw...:" << value.toString(currentState()->context())->toQString();
|
||||
qDebug() << "*** We are about to throw...";
|
||||
}
|
||||
|
||||
FunctionState *Debugger::currentState() const
|
||||
void Debugger::pauseAndWait()
|
||||
{
|
||||
if (_callStack.isEmpty())
|
||||
return 0;
|
||||
else
|
||||
return _callStack.last().state;
|
||||
m_state = Paused;
|
||||
QMetaObject::invokeMethod(m_agent, "debuggerPaused", Qt::QueuedConnection, Q_ARG(QV4::Debugging::Debugger*, this));
|
||||
m_runningCondition.wait(&m_lock);
|
||||
m_state = Running;
|
||||
}
|
||||
|
||||
const char *Debugger::currentArg(unsigned idx) const
|
||||
void Debugger::applyPendingBreakPoints()
|
||||
{
|
||||
FunctionState *state = currentState();
|
||||
return qPrintable(state->argument(idx)->toString(state->context())->toQString());
|
||||
}
|
||||
|
||||
const char *Debugger::currentLocal(unsigned idx) const
|
||||
{
|
||||
FunctionState *state = currentState();
|
||||
return qPrintable(state->local(idx)->toString(state->context())->toQString());
|
||||
}
|
||||
|
||||
const char *Debugger::currentTemp(unsigned idx) const
|
||||
{
|
||||
FunctionState *state = currentState();
|
||||
return qPrintable(state->temp(idx)->toString(state->context())->toQString());
|
||||
}
|
||||
|
||||
void Debugger::printStackTrace() const
|
||||
{
|
||||
for (int i = _callStack.size() - 1; i >=0; --i) {
|
||||
QString n = name(_callStack[i].function);
|
||||
std::cerr << "\tframe #" << i << ": " << qPrintable(n) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int Debugger::callIndex(QV4::ExecutionContext *context)
|
||||
{
|
||||
for (int idx = _callStack.size() - 1; idx >= 0; --idx) {
|
||||
if (_callStack[idx].context == context)
|
||||
return idx;
|
||||
foreach (Function *function, _engine->functions) {
|
||||
m_pendingBreakPointsToAdd.applyToFunction(function, /*removeBreakPoints*/false);
|
||||
m_pendingBreakPointsToRemove.applyToFunction(function, /*removeBreakPoints*/true);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
struct FunctionDebugInfo { // TODO: use opaque d-pointers here
|
||||
QString name;
|
||||
unsigned startLine, startColumn;
|
||||
|
||||
FunctionDebugInfo(V4IR::Function *function):
|
||||
startLine(0), startColumn(0)
|
||||
{
|
||||
if (function->name)
|
||||
name = *function->name;
|
||||
}
|
||||
|
||||
void setSourceLocation(unsigned line, unsigned column)
|
||||
{ startLine = line; startColumn = column; }
|
||||
};
|
||||
|
||||
class FunctionState
|
||||
{
|
||||
public:
|
||||
FunctionState(QV4::ExecutionContext *context);
|
||||
virtual ~FunctionState();
|
||||
|
||||
virtual QV4::Value *argument(unsigned idx);
|
||||
virtual QV4::Value *local(unsigned idx);
|
||||
virtual QV4::Value *temp(unsigned idx) = 0;
|
||||
|
||||
QV4::ExecutionContext *context() const
|
||||
{ return _context; }
|
||||
|
||||
Debugger *debugger() const
|
||||
{ return _context->engine->debugger; }
|
||||
|
||||
private:
|
||||
QV4::ExecutionContext *_context;
|
||||
};
|
||||
|
||||
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)
|
||||
{}
|
||||
};
|
||||
class DebuggerAgent;
|
||||
|
||||
class Q_QML_EXPORT Debugger
|
||||
{
|
||||
public:
|
||||
Debugger(QV4::ExecutionEngine *_engine);
|
||||
enum State {
|
||||
Running,
|
||||
Paused
|
||||
};
|
||||
|
||||
Debugger(ExecutionEngine *_engine);
|
||||
~Debugger();
|
||||
|
||||
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 attachToAgent(DebuggerAgent *agent);
|
||||
void detachFromAgent();
|
||||
|
||||
public: // run-time querying interface
|
||||
FunctionDebugInfo *debugInfo(QV4::FunctionObject *function) const;
|
||||
QString name(QV4::FunctionObject *function) const;
|
||||
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;
|
||||
};
|
||||
|
||||
ExecutionState currentExecutionState(const uchar *code = 0) const;
|
||||
|
||||
bool pauseAtNextOpportunity() const {
|
||||
return m_pauseRequested || m_havePendingBreakPoints;
|
||||
}
|
||||
void setPendingBreakpoints(Function *function);
|
||||
|
||||
public: // compile-time interface
|
||||
void maybeBreakAtInstruction(const uchar *code, bool breakPointHit);
|
||||
|
||||
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;
|
||||
void aboutToThrow(const Value &value);
|
||||
|
||||
private:
|
||||
int callIndex(QV4::ExecutionContext *context);
|
||||
V4IR::Function *irFunction(QV4::Function *vmf) const;
|
||||
// requires lock to be held
|
||||
void pauseAndWait();
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
private: // TODO: use opaque d-pointers here
|
||||
QV4::ExecutionEngine *_engine;
|
||||
QHash<V4IR::Function *, FunctionDebugInfo *> _functionInfo;
|
||||
QHash<QV4::Function *, V4IR::Function *> _vmToIr;
|
||||
QVector<CallInfo> _callStack;
|
||||
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 DebuggerAgent : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
~DebuggerAgent();
|
||||
|
||||
void addDebugger(Debugger *debugger);
|
||||
void removeDebugger(Debugger *debugger);
|
||||
|
||||
void pause(Debugger *debugger);
|
||||
void addBreakPoint(Debugger *debugger, const QString &fileName, int lineNumber);
|
||||
void removeBreakPoint(Debugger *debugger, const QString &fileName, int lineNumber);
|
||||
|
||||
Q_INVOKABLE virtual void debuggerPaused(QV4::Debugging::Debugger *debugger) = 0;
|
||||
|
||||
protected:
|
||||
QList<Debugger *> m_debuggers;
|
||||
};
|
||||
|
||||
} // namespace Debugging
|
||||
} // namespace QQmlJS
|
||||
} // namespace QV4
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
Q_DECLARE_METATYPE(QV4::Debugging::Debugger*)
|
||||
|
||||
#endif // DEBUGGING_H
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -54,7 +54,8 @@ PRIVATETESTS += \
|
|||
qqmlbundle \
|
||||
qrcqml \
|
||||
qqmltimer \
|
||||
qqmlinstantiator
|
||||
qqmlinstantiator \
|
||||
qv4debugger
|
||||
|
||||
qtHaveModule(widgets) {
|
||||
PUBLICTESTS += \
|
||||
|
|
|
@ -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
|
|
@ -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"
|
|
@ -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,16 +286,8 @@ 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;
|
||||
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue