1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQml module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qv4debugjob.h" |
41 | |
42 | #include <private/qv4script_p.h> |
43 | #include <private/qqmlcontext_p.h> |
44 | #include <private/qv4qmlcontext_p.h> |
45 | #include <private/qv4qobjectwrapper_p.h> |
46 | #include <private/qqmldebugservice_p.h> |
47 | #include <private/qv4jscall_p.h> |
48 | |
49 | #include <QtQml/qqmlengine.h> |
50 | |
51 | QT_BEGIN_NAMESPACE |
52 | |
53 | QV4DebugJob::~QV4DebugJob() |
54 | { |
55 | } |
56 | |
57 | JavaScriptJob::JavaScriptJob(QV4::ExecutionEngine *engine, int frameNr, int context, |
58 | const QString &script) : |
59 | engine(engine), frameNr(frameNr), context(context), script(script), |
60 | resultIsException(false) |
61 | {} |
62 | |
63 | void JavaScriptJob::run() |
64 | { |
65 | QV4::Scope scope(engine); |
66 | |
67 | QV4::ScopedContext ctx(scope, engine->currentStackFrame ? engine->currentContext() |
68 | : engine->scriptContext()); |
69 | |
70 | QV4::CppStackFrame *frame = engine->currentStackFrame; |
71 | |
72 | for (int i = 0; frame && i < frameNr; ++i) |
73 | frame = frame->parent; |
74 | if (frameNr > 0 && frame) |
75 | ctx = static_cast<QV4::ExecutionContext *>(&frame->jsFrame->context); |
76 | |
77 | if (context >= 0) { |
78 | QObject *forId = QQmlDebugService::objectForId(id: context); |
79 | QQmlContext * = qmlContext(forId); |
80 | if (extraContext) |
81 | ctx = QV4::QmlContext::create(parent: ctx, context: QQmlContextData::get(context: extraContext), scopeObject: forId); |
82 | } else if (frameNr < 0) { // Use QML context if available |
83 | QQmlEngine *qmlEngine = engine->qmlEngine(); |
84 | if (qmlEngine) { |
85 | QQmlContext *qmlRootContext = qmlEngine->rootContext(); |
86 | QQmlContextPrivate *ctxtPriv = QQmlContextPrivate::get(context: qmlRootContext); |
87 | |
88 | QV4::ScopedObject withContext(scope, engine->newObject()); |
89 | QV4::ScopedString k(scope); |
90 | QV4::ScopedValue v(scope); |
91 | for (int ii = 0; ii < ctxtPriv->instances.count(); ++ii) { |
92 | QObject *object = ctxtPriv->instances.at(i: ii); |
93 | if (QQmlContext *context = qmlContext(object)) { |
94 | if (QQmlContextData *cdata = QQmlContextData::get(context)) { |
95 | v = QV4::QObjectWrapper::wrap(engine, object); |
96 | k = engine->newString(s: cdata->findObjectId(obj: object)); |
97 | withContext->put(name: k, v); |
98 | } |
99 | } |
100 | } |
101 | if (!engine->qmlContext()) |
102 | ctx = QV4::QmlContext::create(parent: ctx, context: QQmlContextData::get(context: qmlRootContext), scopeObject: nullptr); |
103 | } |
104 | } |
105 | |
106 | QV4::Script script(ctx, QV4::Compiler::ContextType::Eval, this->script); |
107 | if (const QV4::Function *function = frame ? frame->v4Function : engine->globalCode) |
108 | script.strictMode = function->isStrict(); |
109 | |
110 | // In order for property lookups in QML to work, we need to disable fast v4 lookups. That |
111 | // is a side-effect of inheritContext. |
112 | script.inheritContext = true; |
113 | script.parse(); |
114 | QV4::ScopedValue result(scope); |
115 | if (!scope.engine->hasException) { |
116 | if (frame) { |
117 | QV4::ScopedValue thisObject(scope, frame->thisObject()); |
118 | result = script.run(thisObject); |
119 | } else { |
120 | result = script.run(); |
121 | } |
122 | } |
123 | if (scope.engine->hasException) { |
124 | result = scope.engine->catchException(); |
125 | resultIsException = true; |
126 | } |
127 | handleResult(result); |
128 | } |
129 | |
130 | bool JavaScriptJob::hasExeption() const |
131 | { |
132 | return resultIsException; |
133 | } |
134 | |
135 | BacktraceJob::BacktraceJob(QV4DataCollector *collector, int fromFrame, int toFrame) : |
136 | CollectJob(collector), fromFrame(fromFrame), toFrame(toFrame) |
137 | { |
138 | } |
139 | |
140 | void BacktraceJob::run() |
141 | { |
142 | QJsonArray frameArray; |
143 | QVector<QV4::StackFrame> frames = collector->engine()->stackTrace(frameLimit: toFrame); |
144 | for (int i = fromFrame; i < toFrame && i < frames.size(); ++i) |
145 | frameArray.push_back(t: collector->buildFrame(stackFrame: frames[i], frameNr: i)); |
146 | if (frameArray.isEmpty()) { |
147 | result.insert(QStringLiteral("totalFrames" ), value: 0); |
148 | } else { |
149 | result.insert(QStringLiteral("fromFrame" ), value: fromFrame); |
150 | result.insert(QStringLiteral("toFrame" ), value: fromFrame + frameArray.size()); |
151 | result.insert(QStringLiteral("frames" ), value: frameArray); |
152 | } |
153 | } |
154 | |
155 | FrameJob::FrameJob(QV4DataCollector *collector, int frameNr) : |
156 | CollectJob(collector), frameNr(frameNr), success(false) |
157 | { |
158 | } |
159 | |
160 | void FrameJob::run() |
161 | { |
162 | QVector<QV4::StackFrame> frames = collector->engine()->stackTrace(frameLimit: frameNr + 1); |
163 | if (frameNr >= frames.length()) { |
164 | success = false; |
165 | } else { |
166 | result = collector->buildFrame(stackFrame: frames[frameNr], frameNr); |
167 | success = true; |
168 | } |
169 | } |
170 | |
171 | bool FrameJob::wasSuccessful() const |
172 | { |
173 | return success; |
174 | } |
175 | |
176 | ScopeJob::ScopeJob(QV4DataCollector *collector, int frameNr, int scopeNr) : |
177 | CollectJob(collector), frameNr(frameNr), scopeNr(scopeNr), success(false) |
178 | { |
179 | } |
180 | |
181 | void ScopeJob::run() |
182 | { |
183 | QJsonObject object; |
184 | success = collector->collectScope(dict: &object, frameNr, scopeNr); |
185 | |
186 | if (success) { |
187 | QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes = |
188 | collector->getScopeTypes(frame: frameNr); |
189 | result[QLatin1String("type" )] = QV4DataCollector::encodeScopeType(scopeType: scopeTypes[scopeNr]); |
190 | } else { |
191 | result[QLatin1String("type" )] = -1; |
192 | } |
193 | result[QLatin1String("index" )] = scopeNr; |
194 | result[QLatin1String("frameIndex" )] = frameNr; |
195 | result[QLatin1String("object" )] = object; |
196 | } |
197 | |
198 | bool ScopeJob::wasSuccessful() const |
199 | { |
200 | return success; |
201 | } |
202 | |
203 | ValueLookupJob::ValueLookupJob(const QJsonArray &handles, QV4DataCollector *collector) : |
204 | CollectJob(collector), handles(handles) {} |
205 | |
206 | void ValueLookupJob::run() |
207 | { |
208 | // Open a QML context if we don't have one, yet. We might run into QML objects when looking up |
209 | // refs and that will crash without a valid QML context. Mind that engine->qmlContext() is only |
210 | // set if the engine is currently executing QML code. |
211 | QScopedPointer<QObject> scopeObject; |
212 | QV4::ExecutionEngine *engine = collector->engine(); |
213 | QV4::Scope scope(engine); |
214 | QV4::Heap::ExecutionContext *qmlContext = nullptr; |
215 | if (engine->qmlEngine() && !engine->qmlContext()) { |
216 | scopeObject.reset(other: new QObject); |
217 | qmlContext = QV4::QmlContext::create(parent: engine->currentContext(), |
218 | context: QQmlContextData::get(context: engine->qmlEngine()->rootContext()), |
219 | scopeObject: scopeObject.data()); |
220 | } |
221 | QV4::ScopedStackFrame frame(scope, qmlContext); |
222 | for (const QJsonValue &handle : handles) { |
223 | QV4DataCollector::Ref ref = handle.toInt(); |
224 | if (!collector->isValidRef(ref)) { |
225 | exception = QString::fromLatin1(str: "Invalid Ref: %1" ).arg(a: ref); |
226 | break; |
227 | } |
228 | result[QString::number(ref)] = collector->lookupRef(ref); |
229 | } |
230 | } |
231 | |
232 | const QString &ValueLookupJob::exceptionMessage() const |
233 | { |
234 | return exception; |
235 | } |
236 | |
237 | ExpressionEvalJob::ExpressionEvalJob(QV4::ExecutionEngine *engine, int frameNr, |
238 | int context, const QString &expression, |
239 | QV4DataCollector *collector) : |
240 | JavaScriptJob(engine, frameNr, context, expression), collector(collector) |
241 | { |
242 | } |
243 | |
244 | void ExpressionEvalJob::handleResult(QV4::ScopedValue &value) |
245 | { |
246 | if (hasExeption()) |
247 | exception = value->toQStringNoThrow(); |
248 | result = collector->lookupRef(ref: collector->addValueRef(value)); |
249 | } |
250 | |
251 | const QString &ExpressionEvalJob::exceptionMessage() const |
252 | { |
253 | return exception; |
254 | } |
255 | |
256 | const QJsonObject &ExpressionEvalJob::returnValue() const |
257 | { |
258 | return result; |
259 | } |
260 | |
261 | GatherSourcesJob::GatherSourcesJob(QV4::ExecutionEngine *engine) |
262 | : engine(engine) |
263 | {} |
264 | |
265 | void GatherSourcesJob::run() |
266 | { |
267 | for (QV4::ExecutableCompilationUnit *unit : engine->compilationUnits) { |
268 | QString fileName = unit->fileName(); |
269 | if (!fileName.isEmpty()) |
270 | sources.append(t: fileName); |
271 | } |
272 | } |
273 | |
274 | const QStringList &GatherSourcesJob::result() const |
275 | { |
276 | return sources; |
277 | } |
278 | |
279 | EvalJob::EvalJob(QV4::ExecutionEngine *engine, const QString &script) : |
280 | JavaScriptJob(engine, /*frameNr*/-1, /*context*/ -1, script), result(false) |
281 | {} |
282 | |
283 | void EvalJob::handleResult(QV4::ScopedValue &result) |
284 | { |
285 | this->result = result->toBoolean(); |
286 | } |
287 | |
288 | bool EvalJob::resultAsBoolean() const |
289 | { |
290 | return result; |
291 | } |
292 | |
293 | QT_END_NAMESPACE |
294 | |