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