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 "qv4debugjob.h"
5
6#include <private/qqmlcontext_p.h>
7#include <private/qqmldebugservice_p.h>
8#include <private/qv4jscall_p.h>
9#include <private/qv4qmlcontext_p.h>
10#include <private/qv4qobjectwrapper_p.h>
11#include <private/qv4script_p.h>
12#include <private/qv4stackframe_p.h>
13
14#include <QtQml/qqmlengine.h>
15
16QT_BEGIN_NAMESPACE
17
18QV4DebugJob::~QV4DebugJob()
19{
20}
21
22JavaScriptJob::JavaScriptJob(QV4::ExecutionEngine *engine, int frameNr, int context,
23 const QString &script) :
24 engine(engine), frameNr(frameNr), context(context), script(script),
25 resultIsException(false)
26{}
27
28void JavaScriptJob::run()
29{
30 QV4::Scope scope(engine);
31
32 QV4::ScopedContext ctx(scope, engine->currentStackFrame ? engine->currentContext()
33 : engine->scriptContext());
34
35 QV4::CppStackFrame *frame = engine->currentStackFrame;
36
37 for (int i = 0; frame && i < frameNr; ++i)
38 frame = frame->parentFrame();
39 if (frameNr > 0 && frame)
40 ctx = frame->context();
41
42 if (context >= 0) {
43 QObject *forId = QQmlDebugService::objectForId(id: context);
44 QQmlContext *extraContext = qmlContext(forId);
45 if (extraContext)
46 ctx = QV4::QmlContext::create(parent: ctx, context: QQmlContextData::get(context: extraContext), scopeObject: forId);
47 } else if (frameNr < 0) { // Use QML context if available
48 QQmlEngine *qmlEngine = engine->qmlEngine();
49 if (qmlEngine) {
50 QQmlContext *qmlRootContext = qmlEngine->rootContext();
51 QQmlContextPrivate *ctxtPriv = QQmlContextPrivate::get(context: qmlRootContext);
52
53 QV4::ScopedObject withContext(scope, engine->newObject());
54 QV4::ScopedString k(scope);
55 QV4::ScopedValue v(scope);
56 const QList<QPointer<QObject>> instances = ctxtPriv->instances();
57 for (const QPointer<QObject> &object : instances) {
58 if (QQmlContext *context = qmlContext(object.data())) {
59 if (QQmlRefPointer<QQmlContextData> cdata = QQmlContextData::get(context)) {
60 v = QV4::QObjectWrapper::wrap(engine, object);
61 k = engine->newString(s: cdata->findObjectId(obj: object));
62 withContext->put(name: k, v);
63 }
64 }
65 }
66 if (!engine->qmlContext())
67 ctx = QV4::QmlContext::create(parent: ctx, context: QQmlContextData::get(context: qmlRootContext), scopeObject: nullptr);
68 }
69 }
70
71 QV4::Script script(ctx, QV4::Compiler::ContextType::Eval, this->script);
72 if (const QV4::Function *function = frame ? frame->v4Function : engine->globalCode)
73 script.strictMode = function->isStrict();
74
75 // In order for property lookups in QML to work, we need to disable fast v4 lookups. That
76 // is a side-effect of inheritContext.
77 script.inheritContext = true;
78 script.parse();
79 QV4::ScopedValue result(scope);
80 if (!scope.hasException()) {
81 if (frame) {
82 QV4::ScopedValue thisObject(scope, frame->thisObject());
83 result = script.run(thisObject);
84 } else {
85 result = script.run();
86 }
87 }
88 if (scope.hasException()) {
89 result = scope.engine->catchException();
90 resultIsException = true;
91 }
92 handleResult(result);
93}
94
95bool JavaScriptJob::hasExeption() const
96{
97 return resultIsException;
98}
99
100BacktraceJob::BacktraceJob(QV4DataCollector *collector, int fromFrame, int toFrame) :
101 CollectJob(collector), fromFrame(fromFrame), toFrame(toFrame)
102{
103}
104
105void BacktraceJob::run()
106{
107 QJsonArray frameArray;
108 QVector<QV4::StackFrame> frames = collector->engine()->stackTrace(frameLimit: toFrame);
109 for (int i = fromFrame; i < toFrame && i < frames.size(); ++i)
110 frameArray.push_back(t: collector->buildFrame(stackFrame: frames[i], frameNr: i));
111 if (frameArray.isEmpty()) {
112 result.insert(QStringLiteral("totalFrames"), value: 0);
113 } else {
114 result.insert(QStringLiteral("fromFrame"), value: fromFrame);
115 result.insert(QStringLiteral("toFrame"), value: fromFrame + frameArray.size());
116 result.insert(QStringLiteral("frames"), value: frameArray);
117 }
118}
119
120FrameJob::FrameJob(QV4DataCollector *collector, int frameNr) :
121 CollectJob(collector), frameNr(frameNr), success(false)
122{
123}
124
125void FrameJob::run()
126{
127 QVector<QV4::StackFrame> frames = collector->engine()->stackTrace(frameLimit: frameNr + 1);
128 if (frameNr >= frames.size()) {
129 success = false;
130 } else {
131 result = collector->buildFrame(stackFrame: frames[frameNr], frameNr);
132 success = true;
133 }
134}
135
136bool FrameJob::wasSuccessful() const
137{
138 return success;
139}
140
141ScopeJob::ScopeJob(QV4DataCollector *collector, int frameNr, int scopeNr) :
142 CollectJob(collector), frameNr(frameNr), scopeNr(scopeNr), success(false)
143{
144}
145
146void ScopeJob::run()
147{
148 QJsonObject object;
149 success = collector->collectScope(dict: &object, frameNr, scopeNr);
150
151 if (success) {
152 QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes =
153 collector->getScopeTypes(frame: frameNr);
154 result[QLatin1String("type")] = QV4DataCollector::encodeScopeType(scopeType: scopeTypes[scopeNr]);
155 } else {
156 result[QLatin1String("type")] = -1;
157 }
158 result[QLatin1String("index")] = scopeNr;
159 result[QLatin1String("frameIndex")] = frameNr;
160 result[QLatin1String("object")] = object;
161}
162
163bool ScopeJob::wasSuccessful() const
164{
165 return success;
166}
167
168ValueLookupJob::ValueLookupJob(const QJsonArray &handles, QV4DataCollector *collector) :
169 CollectJob(collector), handles(handles) {}
170
171void ValueLookupJob::run()
172{
173 // Open a QML context if we don't have one, yet. We might run into QML objects when looking up
174 // refs and that will crash without a valid QML context. Mind that engine->qmlContext() is only
175 // set if the engine is currently executing QML code.
176 QScopedPointer<QObject> scopeObject;
177 QV4::ExecutionEngine *engine = collector->engine();
178 QV4::Scope scope(engine);
179 QV4::Heap::ExecutionContext *qmlContext = engine->qmlContext();
180 if (engine->qmlEngine() && !qmlContext) {
181 scopeObject.reset(other: new QObject);
182 qmlContext = QV4::QmlContext::create(parent: engine->currentContext(),
183 context: QQmlContextData::get(context: engine->qmlEngine()->rootContext()),
184 scopeObject: scopeObject.data());
185 }
186 QV4::Scoped<QV4::ExecutionContext> scopedContext(scope, qmlContext);
187 QV4::ScopedStackFrame frame(scope, scopedContext);
188 for (const QJsonValue handle : handles) {
189 QV4DataCollector::Ref ref = handle.toInt();
190 if (!collector->isValidRef(ref)) {
191 exception = QString::fromLatin1(ba: "Invalid Ref: %1").arg(a: ref);
192 break;
193 }
194 result[QString::number(ref)] = collector->lookupRef(ref);
195 }
196}
197
198const QString &ValueLookupJob::exceptionMessage() const
199{
200 return exception;
201}
202
203ExpressionEvalJob::ExpressionEvalJob(QV4::ExecutionEngine *engine, int frameNr,
204 int context, const QString &expression,
205 QV4DataCollector *collector) :
206 JavaScriptJob(engine, frameNr, context, expression), collector(collector)
207{
208}
209
210void ExpressionEvalJob::handleResult(QV4::ScopedValue &value)
211{
212 if (hasExeption())
213 exception = value->toQStringNoThrow();
214 result = collector->lookupRef(ref: collector->addValueRef(value));
215}
216
217const QString &ExpressionEvalJob::exceptionMessage() const
218{
219 return exception;
220}
221
222const QJsonObject &ExpressionEvalJob::returnValue() const
223{
224 return result;
225}
226
227GatherSourcesJob::GatherSourcesJob(QV4::ExecutionEngine *engine)
228 : engine(engine)
229{}
230
231void GatherSourcesJob::run()
232{
233 for (QV4::ExecutableCompilationUnit *unit : engine->compilationUnits) {
234 QString fileName = unit->fileName();
235 if (!fileName.isEmpty())
236 sources.append(t: fileName);
237 }
238}
239
240const QStringList &GatherSourcesJob::result() const
241{
242 return sources;
243}
244
245EvalJob::EvalJob(QV4::ExecutionEngine *engine, const QString &script) :
246 JavaScriptJob(engine, /*frameNr*/-1, /*context*/ -1, script), result(false)
247{}
248
249void EvalJob::handleResult(QV4::ScopedValue &result)
250{
251 this->result = result->toBoolean();
252}
253
254bool EvalJob::resultAsBoolean() const
255{
256 return result;
257}
258
259QT_END_NAMESPACE
260

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