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 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | QV4Debugger::BreakPoint::BreakPoint(const QString &fileName, int line) |
16 | : fileName(fileName), lineNumber(line) |
17 | {} |
18 | |
19 | inline size_t qHash(const QV4Debugger::BreakPoint &b, size_t seed = 0) noexcept |
20 | { |
21 | return qHash(key: b.fileName, seed) ^ b.lineNumber; |
22 | } |
23 | |
24 | inline bool operator==(const QV4Debugger::BreakPoint &a, |
25 | const QV4Debugger::BreakPoint &b) |
26 | { |
27 | return a.lineNumber == b.lineNumber && a.fileName == b.fileName; |
28 | } |
29 | |
30 | QV4Debugger::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 | |
50 | QV4::ExecutionEngine *QV4Debugger::engine() const |
51 | { |
52 | return m_engine; |
53 | } |
54 | |
55 | const QV4DataCollector *QV4Debugger::collector() const |
56 | { |
57 | return &m_collector; |
58 | } |
59 | |
60 | QV4DataCollector *QV4Debugger::collector() |
61 | { |
62 | return &m_collector; |
63 | } |
64 | |
65 | void QV4Debugger::pause() |
66 | { |
67 | QMutexLocker locker(&m_lock); |
68 | if (m_state == Paused) |
69 | return; |
70 | m_pauseRequested = true; |
71 | } |
72 | |
73 | void 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 | |
87 | QV4Debugger::State QV4Debugger::state() const |
88 | { |
89 | return m_state; |
90 | } |
91 | |
92 | void 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 | |
100 | void 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 | |
108 | void QV4Debugger::setBreakOnThrow(bool onoff) |
109 | { |
110 | QMutexLocker locker(&m_lock); |
111 | |
112 | m_breakOnThrow = onoff; |
113 | } |
114 | |
115 | void QV4Debugger::clearPauseRequest() |
116 | { |
117 | QMutexLocker locker(&m_lock); |
118 | m_pauseRequested = false; |
119 | } |
120 | |
121 | QV4Debugger::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 | |
130 | bool QV4Debugger::pauseAtNextOpportunity() const { |
131 | return m_pauseRequested || m_haveBreakPoints || m_gatherSources || m_stepping >= StepOver; |
132 | } |
133 | |
134 | QVector<QV4::StackFrame> QV4Debugger::stackTrace(int frameLimit) const |
135 | { |
136 | return m_engine->stackTrace(frameLimit); |
137 | } |
138 | |
139 | void 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 | |
178 | void 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 | |
188 | void 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 | |
203 | void 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 | |
215 | QV4::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 | |
223 | void QV4Debugger::runJobUnpaused() |
224 | { |
225 | QMutexLocker locker(&m_lock); |
226 | if (m_runningJob) |
227 | m_runningJob->run(); |
228 | m_jobIsRunning.wakeAll(); |
229 | } |
230 | |
231 | void 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 | |
252 | bool 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 | |
271 | void QV4Debugger::runInEngine(QV4DebugJob *job) |
272 | { |
273 | QMutexLocker locker(&m_lock); |
274 | runInEngine_havingLock(job); |
275 | } |
276 | |
277 | void 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 | |
291 | QT_END_NAMESPACE |
292 | |
293 | #include "moc_qv4debugger.cpp" |
294 | |