diff options
Diffstat (limited to 'src/qml/jsruntime/qv4debugging.cpp')
-rw-r--r-- | src/qml/jsruntime/qv4debugging.cpp | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/src/qml/jsruntime/qv4debugging.cpp b/src/qml/jsruntime/qv4debugging.cpp new file mode 100644 index 0000000000..1b182eac89 --- /dev/null +++ b/src/qml/jsruntime/qv4debugging.cpp @@ -0,0 +1,372 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtQml module 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 "qv4debugging_p.h" +#include "qv4object_p.h" +#include "qv4functionobject_p.h" +#include "qv4function_p.h" +#include "qv4instr_moth_p.h" +#include <iostream> + +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) +{ + qMetaTypeId<Debugger*>(); +} + +Debugger::~Debugger() +{ + detachFromAgent(); +} + +void Debugger::attachToAgent(DebuggerAgent *agent) +{ + Q_ASSERT(!m_agent); + m_agent = agent; +} + +void Debugger::detachFromAgent() +{ + DebuggerAgent *agent = 0; + { + QMutexLocker locker(&m_lock); + agent = m_agent; + m_agent = 0; + } + if (agent) + agent->removeDebugger(this); +} + +void Debugger::pause() +{ + QMutexLocker locker(&m_lock); + if (m_state == Paused) + return; + m_pauseRequested = true; +} + +void Debugger::resume() +{ + QMutexLocker locker(&m_lock); + Q_ASSERT(m_state == Paused); + m_runningCondition.wakeAll(); +} + +void Debugger::addBreakPoint(const QString &fileName, int lineNumber) +{ + 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::removeBreakPoint(const QString &fileName, int lineNumber) +{ + QMutexLocker locker(&m_lock); + if (!m_pendingBreakPointsToAdd.remove(fileName, lineNumber)) + m_pendingBreakPointsToRemove.add(fileName, lineNumber); + m_havePendingBreakPoints = !m_pendingBreakPointsToAdd.isEmpty() || !m_pendingBreakPointsToRemove.isEmpty(); +} + +Debugger::ExecutionState Debugger::currentExecutionState(const uchar *code) const +{ + 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::setPendingBreakpoints(Function *function) +{ + m_pendingBreakPointsToAddToFutureCode.applyToFunction(function, /*removeBreakPoints*/ false); +} + +void Debugger::maybeBreakAtInstruction(const uchar *code, bool breakPointHit) +{ + 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..."; +} + +void Debugger::pauseAndWait() +{ + 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::applyPendingBreakPoints() +{ + foreach (Function *function, _engine->functions) { + m_pendingBreakPointsToAdd.applyToFunction(function, /*removeBreakPoints*/false); + m_pendingBreakPointsToRemove.applyToFunction(function, /*removeBreakPoints*/true); + } + + 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); + } + + m_pendingBreakPointsToAdd.clear(); + m_pendingBreakPointsToRemove.clear(); + m_havePendingBreakPoints = false; +} + +static void realDumpValue(QV4::Value v, QV4::ExecutionContext *ctx, std::string prefix) +{ + using namespace QV4; + using namespace std; + cout << prefix << "tag: " << hex << v.tag << dec << endl << prefix << "\t-> "; + switch (v.type()) { + case Value::Undefined_Type: cout << "Undefined" << endl; return; + case Value::Null_Type: cout << "Null" << endl; return; + case Value::Boolean_Type: cout << "Boolean"; break; + case Value::Integer_Type: cout << "Integer"; break; + case Value::Object_Type: cout << "Object"; break; + case Value::String_Type: cout << "String"; break; + default: cout << "UNKNOWN" << endl; return; + } + cout << endl; + + if (v.isBoolean()) { + cout << prefix << "\t-> " << (v.booleanValue() ? "TRUE" : "FALSE") << endl; + return; + } + + if (v.isInteger()) { + cout << prefix << "\t-> " << v.integerValue() << endl; + return; + } + + if (v.isDouble()) { + cout << prefix << "\t-> " << v.doubleValue() << endl; + return; + } + + if (v.isString()) { + // maybe check something on the Managed object? + cout << prefix << "\t-> @" << hex << v.stringValue() << endl; + cout << prefix << "\t-> \"" << qPrintable(v.stringValue()->toQString()) << "\"" << endl; + return; + } + + Object *o = v.objectValue(); + if (!o) + return; + + cout << prefix << "\t-> @" << hex << o << endl; + cout << prefix << "object type: " << o->internalType() << endl << prefix << "\t-> "; + switch (o->internalType()) { + case QV4::Managed::Type_Invalid: cout << "Invalid"; break; + case QV4::Managed::Type_String: cout << "String"; break; + case QV4::Managed::Type_Object: cout << "Object"; break; + case QV4::Managed::Type_ArrayObject: cout << "ArrayObject"; break; + case QV4::Managed::Type_FunctionObject: cout << "FunctionObject"; break; + case QV4::Managed::Type_BooleanObject: cout << "BooleanObject"; break; + case QV4::Managed::Type_NumberObject: cout << "NumberObject"; break; + case QV4::Managed::Type_StringObject: cout << "StringObject"; break; + case QV4::Managed::Type_DateObject: cout << "DateObject"; break; + case QV4::Managed::Type_RegExpObject: cout << "RegExpObject"; break; + case QV4::Managed::Type_ErrorObject: cout << "ErrorObject"; break; + case QV4::Managed::Type_ArgumentsObject: cout << "ArgumentsObject"; break; + case QV4::Managed::Type_JSONObject: cout << "JSONObject"; break; + case QV4::Managed::Type_MathObject: cout << "MathObject"; break; + case QV4::Managed::Type_ForeachIteratorObject: cout << "ForeachIteratorObject"; break; + default: cout << "UNKNOWN" << endl; return; + } + cout << endl; + + cout << prefix << "properties:" << endl; + ForEachIteratorObject it(ctx, o); + for (Value name = it.nextPropertyName(); !name.isNull(); name = it.nextPropertyName()) { + cout << prefix << "\t\"" << qPrintable(name.stringValue()->toQString()) << "\"" << endl; + PropertyAttributes attrs; + Property *d = o->__getOwnProperty__(name.stringValue(), &attrs); + Value pval = o->getValue(d, attrs); + cout << prefix << "\tvalue:" << endl; + realDumpValue(pval, ctx, prefix + "\t"); + } +} + +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); +} |