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

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