Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qv4debugger.cpp
Go to the documentation of this file.
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"
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
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(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)
33 , m_state(Running)
34 , m_stepping(NotStepping)
35 , m_pauseRequested(false)
36 , m_haveBreakPoints(false)
37 , m_breakOnThrow(false)
39 , m_gatherSources(nullptr)
40 , m_runningJob(nullptr)
42{
43 static int debuggerId = qRegisterMetaType<QV4Debugger*>();
44 static int pauseReasonId = qRegisterMetaType<QV4Debugger::PauseReason>();
45 Q_UNUSED(debuggerId);
46 Q_UNUSED(pauseReasonId);
47 connect(this, &QV4Debugger::scheduleJob,
48 this, &QV4Debugger::runJobUnpaused, Qt::QueuedConnection);
49}
50
52{
53 return m_engine;
54}
55
57{
58 return &m_collector;
59}
60
62{
63 return &m_collector;
64}
65
67{
68 QMutexLocker locker(&m_lock);
69 if (m_state == Paused)
70 return;
71 m_pauseRequested = true;
72}
73
75{
76 QMutexLocker locker(&m_lock);
77 if (m_state != Paused)
78 return;
79
80 if (!m_returnedValue.isUndefined())
81 m_returnedValue.set(m_engine, QV4::Encode::undefined());
82
83 m_currentFrame = m_engine->currentStackFrame;
84 m_stepping = speed;
85 m_runningCondition.wakeAll();
86}
87
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(BreakPoint(fileName.mid(fileName.lastIndexOf('/') + 1),
97 lineNumber), 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(BreakPoint(fileName.mid(fileName.lastIndexOf('/') + 1),
105 lineNumber));
106 m_haveBreakPoints = !m_breakPoints.isEmpty();
107}
108
110{
111 QMutexLocker locker(&m_lock);
112
113 m_breakOnThrow = onoff;
114}
115
117{
118 QMutexLocker locker(&m_lock);
119 m_pauseRequested = false;
120}
121
123{
124 ExecutionState state;
125 state.fileName = QUrl(getFunction()->sourceFile()).fileName();
126 state.lineNumber = engine()->currentStackFrame->lineNumber();
127
128 return state;
129}
130
132 return m_pauseRequested || m_haveBreakPoints || m_gatherSources || m_stepping >= StepOver;
133}
134
136{
137 return m_engine->stackTrace(frameLimit);
138}
139
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(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(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(f->sourceFile(), lineNumber))
174 pauseAndWait(BreakPointHit);
175 }
176 }
177}
178
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(m_engine, retVal);
201 }
202}
203
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(Throwing);
214}
215
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(this, reason);
239
240 while (true) {
241 m_runningCondition.wait(&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 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
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(&m_lock);
289 m_runningJob = nullptr;
290}
291
292QT_END_NAMESPACE
293
294#include "moc_qv4debugger.cpp"
bool resultAsBoolean() const
friend bool operator==(const QByteArray::FromBase64Result &lhs, const QByteArray::FromBase64Result &rhs) noexcept
Returns true if lhs and rhs are equal, otherwise returns false.
Definition qbytearray.h:801
virtual void run()=0
void maybeBreakAtInstruction() override
QV4::Function * getFunction() const
void enteringFunction() override
void runInEngine(QV4DebugJob *job)
QV4DataCollector * collector()
void removeBreakPoint(const QString &fileName, int lineNumber)
QV4::ExecutionEngine * engine() const
void resume(Speed speed)
QVector< QV4::StackFrame > stackTrace(int frameLimit=-1) const
void setBreakOnThrow(bool onoff)
bool pauseAtNextOpportunity() const override
void leavingFunction(const QV4::ReturnedValue &retVal) override
State state() const
ExecutionState currentExecutionState() const
const QV4DataCollector * collector() const
QV4Debugger(QV4::ExecutionEngine *engine)
void aboutToThrow() override
void addBreakPoint(const QString &fileName, int lineNumber, const QString &condition=QString())
void clearPauseRequest()
constexpr size_t qHash(const QSize &s, size_t seed=0) noexcept
Definition qsize.h:191