1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qv4debugger.h"
41#include "qv4debugjob.h"
42#include "qv4datacollector.h"
43
44#include <private/qv4scopedvalue_p.h>
45#include <private/qv4script_p.h>
46#include <private/qqmlcontext_p.h>
47#include <private/qqmlengine_p.h>
48
49QT_BEGIN_NAMESPACE
50
51QV4Debugger::BreakPoint::BreakPoint(const QString &fileName, int line)
52 : fileName(fileName), lineNumber(line)
53{}
54
55inline uint qHash(const QV4Debugger::BreakPoint &b, uint seed = 0) Q_DECL_NOTHROW
56{
57 return qHash(key: b.fileName, seed) ^ b.lineNumber;
58}
59
60inline bool operator==(const QV4Debugger::BreakPoint &a,
61 const QV4Debugger::BreakPoint &b)
62{
63 return a.lineNumber == b.lineNumber && a.fileName == b.fileName;
64}
65
66QV4Debugger::QV4Debugger(QV4::ExecutionEngine *engine)
67 : m_engine(engine)
68 , m_state(Running)
69 , m_stepping(NotStepping)
70 , m_pauseRequested(false)
71 , m_haveBreakPoints(false)
72 , m_breakOnThrow(false)
73 , m_returnedValue(engine, QV4::Value::undefinedValue())
74 , m_gatherSources(nullptr)
75 , m_runningJob(nullptr)
76 , m_collector(engine)
77{
78 static int debuggerId = qRegisterMetaType<QV4Debugger*>();
79 static int pauseReasonId = qRegisterMetaType<QV4Debugger::PauseReason>();
80 Q_UNUSED(debuggerId);
81 Q_UNUSED(pauseReasonId);
82 connect(sender: this, signal: &QV4Debugger::scheduleJob,
83 receiver: this, slot: &QV4Debugger::runJobUnpaused, type: Qt::QueuedConnection);
84}
85
86QV4::ExecutionEngine *QV4Debugger::engine() const
87{
88 return m_engine;
89}
90
91const QV4DataCollector *QV4Debugger::collector() const
92{
93 return &m_collector;
94}
95
96QV4DataCollector *QV4Debugger::collector()
97{
98 return &m_collector;
99}
100
101void QV4Debugger::pause()
102{
103 QMutexLocker locker(&m_lock);
104 if (m_state == Paused)
105 return;
106 m_pauseRequested = true;
107}
108
109void QV4Debugger::resume(Speed speed)
110{
111 QMutexLocker locker(&m_lock);
112 if (m_state != Paused)
113 return;
114
115 if (!m_returnedValue.isUndefined())
116 m_returnedValue.set(engine: m_engine, value: QV4::Encode::undefined());
117
118 m_currentFrame = m_engine->currentStackFrame;
119 m_stepping = speed;
120 m_runningCondition.wakeAll();
121}
122
123QV4Debugger::State QV4Debugger::state() const
124{
125 return m_state;
126}
127
128void QV4Debugger::addBreakPoint(const QString &fileName, int lineNumber, const QString &condition)
129{
130 QMutexLocker locker(&m_lock);
131 m_breakPoints.insert(key: BreakPoint(fileName.mid(position: fileName.lastIndexOf(c: '/') + 1),
132 lineNumber), value: condition);
133 m_haveBreakPoints = true;
134}
135
136void QV4Debugger::removeBreakPoint(const QString &fileName, int lineNumber)
137{
138 QMutexLocker locker(&m_lock);
139 m_breakPoints.remove(key: BreakPoint(fileName.mid(position: fileName.lastIndexOf(c: '/') + 1),
140 lineNumber));
141 m_haveBreakPoints = !m_breakPoints.isEmpty();
142}
143
144void QV4Debugger::setBreakOnThrow(bool onoff)
145{
146 QMutexLocker locker(&m_lock);
147
148 m_breakOnThrow = onoff;
149}
150
151void QV4Debugger::clearPauseRequest()
152{
153 QMutexLocker locker(&m_lock);
154 m_pauseRequested = false;
155}
156
157QV4Debugger::ExecutionState QV4Debugger::currentExecutionState() const
158{
159 ExecutionState state;
160 state.fileName = QUrl(getFunction()->sourceFile()).fileName();
161 state.lineNumber = engine()->currentStackFrame->lineNumber();
162
163 return state;
164}
165
166bool QV4Debugger::pauseAtNextOpportunity() const {
167 return m_pauseRequested || m_haveBreakPoints || m_gatherSources || m_stepping >= StepOver;
168}
169
170QVector<QV4::StackFrame> QV4Debugger::stackTrace(int frameLimit) const
171{
172 return m_engine->stackTrace(frameLimit);
173}
174
175void QV4Debugger::maybeBreakAtInstruction()
176{
177 if (m_runningJob) // do not re-enter when we're doing a job for the debugger.
178 return;
179
180 QMutexLocker locker(&m_lock);
181
182 if (m_gatherSources) {
183 m_gatherSources->run();
184 delete m_gatherSources;
185 m_gatherSources = nullptr;
186 }
187
188 switch (m_stepping) {
189 case StepOver:
190 if (m_currentFrame != m_engine->currentStackFrame)
191 break;
192 Q_FALLTHROUGH();
193 case StepIn:
194 pauseAndWait(reason: Step);
195 return;
196 case StepOut:
197 case NotStepping:
198 break;
199 }
200
201 if (m_pauseRequested) { // Serve debugging requests from the agent
202 m_pauseRequested = false;
203 pauseAndWait(reason: PauseRequest);
204 } else if (m_haveBreakPoints) {
205 if (QV4::Function *f = getFunction()) {
206 // lineNumber will be negative for Ret instructions, so those won't match
207 const int lineNumber = engine()->currentStackFrame->lineNumber();
208 if (reallyHitTheBreakPoint(filename: f->sourceFile(), linenr: lineNumber))
209 pauseAndWait(reason: BreakPointHit);
210 }
211 }
212}
213
214void QV4Debugger::enteringFunction()
215{
216 if (m_runningJob)
217 return;
218 QMutexLocker locker(&m_lock);
219
220 if (m_stepping == StepIn)
221 m_currentFrame = m_engine->currentStackFrame;
222}
223
224void QV4Debugger::leavingFunction(const QV4::ReturnedValue &retVal)
225{
226 if (m_runningJob)
227 return;
228 Q_UNUSED(retVal); // TODO
229
230 QMutexLocker locker(&m_lock);
231
232 if (m_stepping != NotStepping && m_currentFrame == m_engine->currentStackFrame) {
233 m_currentFrame = m_currentFrame->parent;
234 m_stepping = StepOver;
235 m_returnedValue.set(engine: m_engine, value: retVal);
236 }
237}
238
239void QV4Debugger::aboutToThrow()
240{
241 if (!m_breakOnThrow)
242 return;
243
244 if (m_runningJob) // do not re-enter when we're doing a job for the debugger.
245 return;
246
247 QMutexLocker locker(&m_lock);
248 pauseAndWait(reason: Throwing);
249}
250
251QV4::Function *QV4Debugger::getFunction() const
252{
253 if (m_engine->currentStackFrame)
254 return m_engine->currentStackFrame->v4Function;
255 else
256 return m_engine->globalCode;
257}
258
259void QV4Debugger::runJobUnpaused()
260{
261 QMutexLocker locker(&m_lock);
262 if (m_runningJob)
263 m_runningJob->run();
264 m_jobIsRunning.wakeAll();
265}
266
267void QV4Debugger::pauseAndWait(PauseReason reason)
268{
269 if (m_runningJob)
270 return;
271
272 m_state = Paused;
273 emit debuggerPaused(self: this, reason);
274
275 while (true) {
276 m_runningCondition.wait(lockedMutex: &m_lock);
277 if (m_runningJob) {
278 m_runningJob->run();
279 m_jobIsRunning.wakeAll();
280 } else {
281 break;
282 }
283 }
284
285 m_state = Running;
286}
287
288bool QV4Debugger::reallyHitTheBreakPoint(const QString &filename, int linenr)
289{
290 QHash<BreakPoint, QString>::iterator it = m_breakPoints.find(
291 key: BreakPoint(QUrl(filename).fileName(), linenr));
292 if (it == m_breakPoints.end())
293 return false;
294 QString condition = it.value();
295 if (condition.isEmpty())
296 return true;
297
298 Q_ASSERT(m_runningJob == nullptr);
299 EvalJob evilJob(m_engine, condition);
300 m_runningJob = &evilJob;
301 m_runningJob->run();
302 m_runningJob = nullptr;
303
304 return evilJob.resultAsBoolean();
305}
306
307void QV4Debugger::runInEngine(QV4DebugJob *job)
308{
309 QMutexLocker locker(&m_lock);
310 runInEngine_havingLock(job);
311}
312
313void QV4Debugger::runInEngine_havingLock(QV4DebugJob *job)
314{
315 Q_ASSERT(job);
316 Q_ASSERT(m_runningJob == nullptr);
317
318 m_runningJob = job;
319 if (state() == Paused)
320 m_runningCondition.wakeAll();
321 else
322 emit scheduleJob();
323 m_jobIsRunning.wait(lockedMutex: &m_lock);
324 m_runningJob = nullptr;
325}
326
327QT_END_NAMESPACE
328
329#include "moc_qv4debugger.cpp"
330

source code of qtdeclarative/src/plugins/qmltooling/qmldbg_debugger/qv4debugger.cpp