1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qv4debugger.h"
5#include "qv4debugjob.h"
6#include "qv4datacollector.h"
7
8#include <private/qqmlcontext_p.h>
9#include <private/qqmlengine_p.h>
10#include <private/qv4scopedvalue_p.h>
11#include <private/qv4script_p.h>
12#include <private/qv4stackframe_p.h>
13
14QT_BEGIN_NAMESPACE
15
16QV4Debugger::BreakPoint::BreakPoint(const QString &fileName, int line)
17 : fileName(fileName), lineNumber(line)
18{}
19
20inline size_t qHash(const QV4Debugger::BreakPoint &b, size_t seed = 0) noexcept
21{
22 return qHash(key: b.fileName, seed) ^ b.lineNumber;
23}
24
25inline bool operator==(const QV4Debugger::BreakPoint &a,
26 const QV4Debugger::BreakPoint &b)
27{
28 return a.lineNumber == b.lineNumber && a.fileName == b.fileName;
29}
30
31QV4Debugger::QV4Debugger(QV4::ExecutionEngine *engine)
32 : m_engine(engine)
33 , m_state(Running)
34 , m_stepping(NotStepping)
35 , m_pauseRequested(false)
36 , m_haveBreakPoints(false)
37 , m_breakOnThrow(false)
38 , m_returnedValue(engine, QV4::Value::undefinedValue())
39 , m_gatherSources(nullptr)
40 , m_runningJob(nullptr)
41 , m_collector(engine)
42{
43 static int debuggerId = qRegisterMetaType<QV4Debugger*>();
44 static int pauseReasonId = qRegisterMetaType<QV4Debugger::PauseReason>();
45 Q_UNUSED(debuggerId);
46 Q_UNUSED(pauseReasonId);
47 connect(sender: this, signal: &QV4Debugger::scheduleJob,
48 context: this, slot: &QV4Debugger::runJobUnpaused, type: Qt::QueuedConnection);
49}
50
51QV4::ExecutionEngine *QV4Debugger::engine() const
52{
53 return m_engine;
54}
55
56const QV4DataCollector *QV4Debugger::collector() const
57{
58 return &m_collector;
59}
60
61QV4DataCollector *QV4Debugger::collector()
62{
63 return &m_collector;
64}
65
66void QV4Debugger::pause()
67{
68 QMutexLocker locker(&m_lock);
69 if (m_state == Paused)
70 return;
71 m_pauseRequested = true;
72}
73
74void QV4Debugger::resume(Speed speed)
75{
76 QMutexLocker locker(&m_lock);
77 if (m_state != Paused)
78 return;
79
80 if (!m_returnedValue.isUndefined())
81 m_returnedValue.set(engine: m_engine, value: QV4::Encode::undefined());
82
83 m_currentFrame = m_engine->currentStackFrame;
84 m_stepping = speed;
85 m_runningCondition.wakeAll();
86}
87
88QV4Debugger::State QV4Debugger::state() const
89{
90 return m_state;
91}
92
93void QV4Debugger::addBreakPoint(const QString &fileName, int lineNumber, const QString &condition)
94{
95 QMutexLocker locker(&m_lock);
96 m_breakPoints.insert(key: BreakPoint(fileName.mid(position: fileName.lastIndexOf(c: '/') + 1),
97 lineNumber), value: condition);
98 m_haveBreakPoints = true;
99}
100
101void QV4Debugger::removeBreakPoint(const QString &fileName, int lineNumber)
102{
103 QMutexLocker locker(&m_lock);
104 m_breakPoints.remove(key: BreakPoint(fileName.mid(position: fileName.lastIndexOf(c: '/') + 1),
105 lineNumber));
106 m_haveBreakPoints = !m_breakPoints.isEmpty();
107}
108
109void QV4Debugger::setBreakOnThrow(bool onoff)
110{
111 QMutexLocker locker(&m_lock);
112
113 m_breakOnThrow = onoff;
114}
115
116void QV4Debugger::clearPauseRequest()
117{
118 QMutexLocker locker(&m_lock);
119 m_pauseRequested = false;
120}
121
122QV4Debugger::ExecutionState QV4Debugger::currentExecutionState() const
123{
124 ExecutionState state;
125 state.fileName = QUrl(getFunction()->sourceFile()).fileName();
126 state.lineNumber = engine()->currentStackFrame->lineNumber();
127
128 return state;
129}
130
131bool QV4Debugger::pauseAtNextOpportunity() const {
132 return m_pauseRequested || m_haveBreakPoints || m_gatherSources || m_stepping >= StepOver;
133}
134
135QVector<QV4::StackFrame> QV4Debugger::stackTrace(int frameLimit) const
136{
137 return m_engine->stackTrace(frameLimit);
138}
139
140void QV4Debugger::maybeBreakAtInstruction()
141{
142 if (m_runningJob) // do not re-enter when we're doing a job for the debugger.
143 return;
144
145 QMutexLocker locker(&m_lock);
146
147 if (m_gatherSources) {
148 m_gatherSources->run();
149 delete m_gatherSources;
150 m_gatherSources = nullptr;
151 }
152
153 switch (m_stepping) {
154 case StepOver:
155 if (m_currentFrame != m_engine->currentStackFrame)
156 break;
157 Q_FALLTHROUGH();
158 case StepIn:
159 pauseAndWait(reason: Step);
160 return;
161 case StepOut:
162 case NotStepping:
163 break;
164 }
165
166 if (m_pauseRequested) { // Serve debugging requests from the agent
167 m_pauseRequested = false;
168 pauseAndWait(reason: PauseRequest);
169 } else if (m_haveBreakPoints) {
170 if (QV4::Function *f = getFunction()) {
171 // lineNumber will be negative for Ret instructions, so those won't match
172 const int lineNumber = engine()->currentStackFrame->lineNumber();
173 if (reallyHitTheBreakPoint(filename: f->sourceFile(), linenr: lineNumber))
174 pauseAndWait(reason: BreakPointHit);
175 }
176 }
177}
178
179void QV4Debugger::enteringFunction()
180{
181 if (m_runningJob)
182 return;
183 QMutexLocker locker(&m_lock);
184
185 if (m_stepping == StepIn)
186 m_currentFrame = m_engine->currentStackFrame;
187}
188
189void QV4Debugger::leavingFunction(const QV4::ReturnedValue &retVal)
190{
191 if (m_runningJob)
192 return;
193 Q_UNUSED(retVal); // TODO
194
195 QMutexLocker locker(&m_lock);
196
197 if (m_stepping != NotStepping && m_currentFrame == m_engine->currentStackFrame) {
198 m_currentFrame = m_currentFrame->parentFrame();
199 m_stepping = StepOver;
200 m_returnedValue.set(engine: m_engine, value: retVal);
201 }
202}
203
204void QV4Debugger::aboutToThrow()
205{
206 if (!m_breakOnThrow)
207 return;
208
209 if (m_runningJob) // do not re-enter when we're doing a job for the debugger.
210 return;
211
212 QMutexLocker locker(&m_lock);
213 pauseAndWait(reason: Throwing);
214}
215
216QV4::Function *QV4Debugger::getFunction() const
217{
218 if (m_engine->currentStackFrame)
219 return m_engine->currentStackFrame->v4Function;
220 else
221 return m_engine->globalCode;
222}
223
224void QV4Debugger::runJobUnpaused()
225{
226 QMutexLocker locker(&m_lock);
227 if (m_runningJob)
228 m_runningJob->run();
229 m_jobIsRunning.wakeAll();
230}
231
232void QV4Debugger::pauseAndWait(PauseReason reason)
233{
234 if (m_runningJob)
235 return;
236
237 m_state = Paused;
238 emit debuggerPaused(self: this, reason);
239
240 while (true) {
241 m_runningCondition.wait(lockedMutex: &m_lock);
242 if (m_runningJob) {
243 m_runningJob->run();
244 m_jobIsRunning.wakeAll();
245 } else {
246 break;
247 }
248 }
249
250 m_state = Running;
251}
252
253bool QV4Debugger::reallyHitTheBreakPoint(const QString &filename, int linenr)
254{
255 const auto it = m_breakPoints.constFind(
256 key: BreakPoint(QUrl(filename).fileName(), linenr));
257 if (it == m_breakPoints.cend())
258 return false;
259 QString condition = it.value();
260 if (condition.isEmpty())
261 return true;
262
263 Q_ASSERT(m_runningJob == nullptr);
264 EvalJob evilJob(m_engine, condition);
265 m_runningJob = &evilJob;
266 m_runningJob->run();
267 m_runningJob = nullptr;
268
269 return evilJob.resultAsBoolean();
270}
271
272void QV4Debugger::runInEngine(QV4DebugJob *job)
273{
274 QMutexLocker locker(&m_lock);
275 runInEngine_havingLock(job);
276}
277
278void QV4Debugger::runInEngine_havingLock(QV4DebugJob *job)
279{
280 Q_ASSERT(job);
281 Q_ASSERT(m_runningJob == nullptr);
282
283 m_runningJob = job;
284 if (state() == Paused)
285 m_runningCondition.wakeAll();
286 else
287 emit scheduleJob();
288 m_jobIsRunning.wait(lockedMutex: &m_lock);
289 m_runningJob = nullptr;
290}
291
292QT_END_NAMESPACE
293
294#include "moc_qv4debugger.cpp"
295

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