blob: 5521e7628b17356a91a90e38d144b5e782d97e7b [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qv4debugger.h"
#include "qv4debugjob.h"
#include "qv4datacollector.h"
#include <private/qv4scopedvalue_p.h>
#include <private/qv4script_p.h>
#include <private/qqmlcontext_p.h>
#include <private/qqmlengine_p.h>
QT_BEGIN_NAMESPACE
QV4Debugger::BreakPoint::BreakPoint(const QString &fileName, int line)
: fileName(fileName), lineNumber(line)
{}
inline uint qHash(const QV4Debugger::BreakPoint &b, uint seed = 0) Q_DECL_NOTHROW
{
return qHash(b.fileName, seed) ^ b.lineNumber;
}
inline bool operator==(const QV4Debugger::BreakPoint &a,
const QV4Debugger::BreakPoint &b)
{
return a.lineNumber == b.lineNumber && a.fileName == b.fileName;
}
QV4Debugger::QV4Debugger(QV4::ExecutionEngine *engine)
: m_engine(engine)
, m_state(Running)
, m_stepping(NotStepping)
, m_pauseRequested(false)
, m_haveBreakPoints(false)
, m_breakOnThrow(false)
, m_returnedValue(engine, QV4::Value::undefinedValue())
, m_gatherSources(nullptr)
, m_runningJob(nullptr)
, m_collector(engine)
{
static int debuggerId = qRegisterMetaType<QV4Debugger*>();
static int pauseReasonId = qRegisterMetaType<QV4Debugger::PauseReason>();
Q_UNUSED(debuggerId);
Q_UNUSED(pauseReasonId);
connect(this, &QV4Debugger::scheduleJob,
this, &QV4Debugger::runJobUnpaused, Qt::QueuedConnection);
}
QV4::ExecutionEngine *QV4Debugger::engine() const
{
return m_engine;
}
const QV4DataCollector *QV4Debugger::collector() const
{
return &m_collector;
}
QV4DataCollector *QV4Debugger::collector()
{
return &m_collector;
}
void QV4Debugger::pause()
{
QMutexLocker locker(&m_lock);
if (m_state == Paused)
return;
m_pauseRequested = true;
}
void QV4Debugger::resume(Speed speed)
{
QMutexLocker locker(&m_lock);
if (m_state != Paused)
return;
if (!m_returnedValue.isUndefined())
m_returnedValue.set(m_engine, QV4::Encode::undefined());
m_currentFrame = m_engine->currentStackFrame;
m_stepping = speed;
m_runningCondition.wakeAll();
}
QV4Debugger::State QV4Debugger::state() const
{
return m_state;
}
void QV4Debugger::addBreakPoint(const QString &fileName, int lineNumber, const QString &condition)
{
QMutexLocker locker(&m_lock);
m_breakPoints.insert(BreakPoint(fileName.mid(fileName.lastIndexOf('/') + 1),
lineNumber), condition);
m_haveBreakPoints = true;
}
void QV4Debugger::removeBreakPoint(const QString &fileName, int lineNumber)
{
QMutexLocker locker(&m_lock);
m_breakPoints.remove(BreakPoint(fileName.mid(fileName.lastIndexOf('/') + 1),
lineNumber));
m_haveBreakPoints = !m_breakPoints.isEmpty();
}
void QV4Debugger::setBreakOnThrow(bool onoff)
{
QMutexLocker locker(&m_lock);
m_breakOnThrow = onoff;
}
void QV4Debugger::clearPauseRequest()
{
QMutexLocker locker(&m_lock);
m_pauseRequested = false;
}
QV4Debugger::ExecutionState QV4Debugger::currentExecutionState() const
{
ExecutionState state;
state.fileName = QUrl(getFunction()->sourceFile()).fileName();
state.lineNumber = engine()->currentStackFrame->lineNumber();
return state;
}
bool QV4Debugger::pauseAtNextOpportunity() const {
return m_pauseRequested || m_haveBreakPoints || m_gatherSources || m_stepping >= StepOver;
}
QVector<QV4::StackFrame> QV4Debugger::stackTrace(int frameLimit) const
{
return m_engine->stackTrace(frameLimit);
}
void QV4Debugger::maybeBreakAtInstruction()
{
if (m_runningJob) // do not re-enter when we're doing a job for the debugger.
return;
QMutexLocker locker(&m_lock);
if (m_gatherSources) {
m_gatherSources->run();
delete m_gatherSources;
m_gatherSources = nullptr;
}
switch (m_stepping) {
case StepOver:
if (m_currentFrame != m_engine->currentStackFrame)
break;
Q_FALLTHROUGH();
case StepIn:
pauseAndWait(Step);
return;
case StepOut:
case NotStepping:
break;
}
if (m_pauseRequested) { // Serve debugging requests from the agent
m_pauseRequested = false;
pauseAndWait(PauseRequest);
} else if (m_haveBreakPoints) {
if (QV4::Function *f = getFunction()) {
// lineNumber will be negative for Ret instructions, so those won't match
const int lineNumber = engine()->currentStackFrame->lineNumber();
if (reallyHitTheBreakPoint(f->sourceFile(), lineNumber))
pauseAndWait(BreakPointHit);
}
}
}
void QV4Debugger::enteringFunction()
{
if (m_runningJob)
return;
QMutexLocker locker(&m_lock);
if (m_stepping == StepIn)
m_currentFrame = m_engine->currentStackFrame;
}
void QV4Debugger::leavingFunction(const QV4::ReturnedValue &retVal)
{
if (m_runningJob)
return;
Q_UNUSED(retVal); // TODO
QMutexLocker locker(&m_lock);
if (m_stepping != NotStepping && m_currentFrame == m_engine->currentStackFrame) {
m_currentFrame = m_currentFrame->parent;
m_stepping = StepOver;
m_returnedValue.set(m_engine, retVal);
}
}
void QV4Debugger::aboutToThrow()
{
if (!m_breakOnThrow)
return;
if (m_runningJob) // do not re-enter when we're doing a job for the debugger.
return;
QMutexLocker locker(&m_lock);
pauseAndWait(Throwing);
}
QV4::Function *QV4Debugger::getFunction() const
{
if (m_engine->currentStackFrame)
return m_engine->currentStackFrame->v4Function;
else
return m_engine->globalCode;
}
void QV4Debugger::runJobUnpaused()
{
QMutexLocker locker(&m_lock);
if (m_runningJob)
m_runningJob->run();
m_jobIsRunning.wakeAll();
}
void QV4Debugger::pauseAndWait(PauseReason reason)
{
if (m_runningJob)
return;
m_state = Paused;
emit debuggerPaused(this, reason);
while (true) {
m_runningCondition.wait(&m_lock);
if (m_runningJob) {
m_runningJob->run();
m_jobIsRunning.wakeAll();
} else {
break;
}
}
m_state = Running;
}
bool QV4Debugger::reallyHitTheBreakPoint(const QString &filename, int linenr)
{
QHash<BreakPoint, QString>::iterator it = m_breakPoints.find(
BreakPoint(QUrl(filename).fileName(), linenr));
if (it == m_breakPoints.end())
return false;
QString condition = it.value();
if (condition.isEmpty())
return true;
Q_ASSERT(m_runningJob == nullptr);
EvalJob evilJob(m_engine, condition);
m_runningJob = &evilJob;
m_runningJob->run();
m_runningJob = nullptr;
return evilJob.resultAsBoolean();
}
void QV4Debugger::runInEngine(QV4DebugJob *job)
{
QMutexLocker locker(&m_lock);
runInEngine_havingLock(job);
}
void QV4Debugger::runInEngine_havingLock(QV4DebugJob *job)
{
Q_ASSERT(job);
Q_ASSERT(m_runningJob == nullptr);
m_runningJob = job;
if (state() == Paused)
m_runningCondition.wakeAll();
else
emit scheduleJob();
m_jobIsRunning.wait(&m_lock);
m_runningJob = nullptr;
}
QT_END_NAMESPACE
#include "moc_qv4debugger.cpp"