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 "qv4datacollector.h"
5#include "qv4debugger.h"
6#include "qv4debugjob.h"
7
8#include <private/qv4script_p.h>
9#include <private/qv4string_p.h>
10#include <private/qv4objectiterator_p.h>
11#include <private/qv4identifierhash_p.h>
12#include <private/qv4runtime_p.h>
13#include <private/qv4identifiertable_p.h>
14
15#include <private/qqmlcontext_p.h>
16#include <private/qqmlengine_p.h>
17
18#include <QtCore/qjsonarray.h>
19#include <QtCore/qjsonobject.h>
20
21QT_BEGIN_NAMESPACE
22
23QV4::CppStackFrame *QV4DataCollector::findFrame(int frame)
24{
25 QV4::CppStackFrame *f = engine()->currentStackFrame;
26 while (f && frame) {
27 --frame;
28 f = f->parentFrame();
29 }
30 return f;
31}
32
33QV4::Heap::ExecutionContext *QV4DataCollector::findContext(int frame)
34{
35 QV4::CppStackFrame *f = findFrame(frame);
36
37 return f ? f->context()->d() : nullptr;
38}
39
40QV4::Heap::ExecutionContext *QV4DataCollector::findScope(QV4::Heap::ExecutionContext *ctx, int scope)
41{
42 for (; scope > 0 && ctx; --scope)
43 ctx = ctx->outer;
44
45 return ctx;
46}
47
48QVector<QV4::Heap::ExecutionContext::ContextType> QV4DataCollector::getScopeTypes(int frame)
49{
50 QVector<QV4::Heap::ExecutionContext::ContextType> types;
51
52 QV4::Heap::ExecutionContext *it = findFrame(frame)->context()->d();
53
54 for (; it; it = it->outer)
55 types.append(t: QV4::Heap::ExecutionContext::ContextType(it->type));
56
57 return types;
58}
59
60int QV4DataCollector::encodeScopeType(QV4::Heap::ExecutionContext::ContextType scopeType)
61{
62 switch (scopeType) {
63 case QV4::Heap::ExecutionContext::Type_GlobalContext:
64 break;
65 case QV4::Heap::ExecutionContext::Type_WithContext:
66 return 2;
67 case QV4::Heap::ExecutionContext::Type_CallContext:
68 return 1;
69 case QV4::Heap::ExecutionContext::Type_QmlContext:
70 return 3;
71 case QV4::Heap::ExecutionContext::Type_BlockContext:
72 return 4;
73 }
74 return 0;
75}
76
77QV4DataCollector::QV4DataCollector(QV4::ExecutionEngine *engine)
78 : m_engine(engine)
79{
80 m_values.set(engine, obj: engine->newArrayObject());
81}
82
83QV4DataCollector::Ref QV4DataCollector::addValueRef(const QV4::ScopedValue &value)
84{
85 return addRef(value);
86}
87
88const QV4::Object *collectProperty(const QV4::ScopedValue &value, QV4::ExecutionEngine *engine,
89 QJsonObject &dict)
90{
91 QV4::Scope scope(engine);
92 QV4::ScopedValue typeString(scope, QV4::Runtime::TypeofValue::call(engine, value));
93 dict.insert(QStringLiteral("type"), value: typeString->toQStringNoThrow());
94
95 const QLatin1String valueKey("value");
96 switch (value->type()) {
97 case QV4::Value::Empty_Type:
98 Q_ASSERT(!"empty Value encountered");
99 return nullptr;
100 case QV4::Value::Undefined_Type:
101 dict.insert(key: valueKey, value: QJsonValue::Undefined);
102 return nullptr;
103 case QV4::Value::Null_Type:
104 dict.insert(key: valueKey, value: QJsonValue::Null);
105 return nullptr;
106 case QV4::Value::Boolean_Type:
107 dict.insert(key: valueKey, value: value->booleanValue());
108 return nullptr;
109 case QV4::Value::Managed_Type:
110 if (const QV4::String *s = value->as<QV4::String>()) {
111 dict.insert(key: valueKey, value: s->toQString());
112 } else if (const QV4::ArrayObject *a = value->as<QV4::ArrayObject>()) {
113 // size of an array is number of its numerical properties; We don't consider free form
114 // object properties here.
115 dict.insert(key: valueKey, value: qint64(a->getLength()));
116 return a;
117 } else if (const QV4::Object *o = value->as<QV4::Object>()) {
118 int numProperties = 0;
119 QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly);
120 QV4::PropertyAttributes attrs;
121 QV4::ScopedPropertyKey name(scope);
122 while (true) {
123 name = it.next(pd: nullptr, attributes: &attrs);
124 if (!name->isValid())
125 break;
126 ++numProperties;
127 }
128 dict.insert(key: valueKey, value: numProperties);
129 return o;
130 } else {
131 Q_UNREACHABLE();
132 }
133 return nullptr;
134 case QV4::Value::Integer_Type:
135 dict.insert(key: valueKey, value: value->integerValue());
136 return nullptr;
137 default: {// double
138 const double val = value->doubleValue();
139 if (qIsFinite(d: val))
140 dict.insert(key: valueKey, value: val);
141 else if (qIsNaN(d: val))
142 dict.insert(key: valueKey, QStringLiteral("NaN"));
143 else if (val < 0)
144 dict.insert(key: valueKey, QStringLiteral("-Infinity"));
145 else
146 dict.insert(key: valueKey, QStringLiteral("Infinity"));
147 return nullptr;
148 }
149 }
150}
151
152QJsonObject QV4DataCollector::lookupRef(Ref ref)
153{
154 QJsonObject dict;
155
156 dict.insert(QStringLiteral("handle"), value: qint64(ref));
157 QV4::Scope scope(engine());
158 QV4::ScopedValue value(scope, getValue(ref));
159
160 const QV4::Object *object = collectProperty(value, engine: engine(), dict);
161 if (object)
162 dict.insert(QStringLiteral("properties"), value: collectProperties(object));
163
164 return dict;
165}
166
167bool QV4DataCollector::isValidRef(QV4DataCollector::Ref ref) const
168{
169 QV4::Scope scope(engine());
170 QV4::ScopedObject array(scope, m_values.value());
171 return ref < array->getLength();
172}
173
174bool QV4DataCollector::collectScope(QJsonObject *dict, int frameNr, int scopeNr)
175{
176 QV4::Scope scope(engine());
177
178 QV4::Scoped<QV4::ExecutionContext> ctxt(scope, findScope(ctx: findContext(frame: frameNr), scope: scopeNr));
179 if (!ctxt)
180 return false;
181
182 QV4::ScopedObject scopeObject(scope, engine()->newObject());
183 if (ctxt->d()->type == QV4::Heap::ExecutionContext::Type_CallContext ||
184 ctxt->d()->type == QV4::Heap::ExecutionContext::Type_BlockContext) {
185 QStringList names;
186 Refs collectedRefs;
187
188 QV4::ScopedValue v(scope);
189 QV4::Heap::InternalClass *ic = ctxt->internalClass();
190 for (uint i = 0; i < ic->size; ++i) {
191 QString name = ic->keyAt(index: i);
192 names.append(t: name);
193 v = static_cast<QV4::Heap::CallContext *>(ctxt->d())->locals[i];
194 collectedRefs.append(t: addValueRef(value: v));
195 }
196
197 Q_ASSERT(names.size() == collectedRefs.size());
198 QV4::ScopedString propName(scope);
199 for (int i = 0, ei = collectedRefs.size(); i != ei; ++i) {
200 propName = engine()->newString(s: names.at(i));
201 scopeObject->put(name: propName, v: (v = getValue(ref: collectedRefs.at(i))));
202 }
203 }
204
205 *dict = lookupRef(ref: addRef(value: scopeObject));
206
207 return true;
208}
209
210QJsonObject toRef(QV4DataCollector::Ref ref) {
211 QJsonObject dict;
212 dict.insert(QStringLiteral("ref"), value: qint64(ref));
213 return dict;
214}
215
216QJsonObject QV4DataCollector::buildFrame(const QV4::StackFrame &stackFrame, int frameNr)
217{
218 QJsonObject frame;
219 frame[QLatin1String("index")] = frameNr;
220 frame[QLatin1String("debuggerFrame")] = false;
221 frame[QLatin1String("func")] = stackFrame.function;
222 frame[QLatin1String("script")] = stackFrame.source;
223 frame[QLatin1String("line")] = qAbs(t: stackFrame.line) - 1;
224 if (stackFrame.column >= 0)
225 frame[QLatin1String("column")] = stackFrame.column;
226
227 QJsonArray scopes;
228 QV4::Scope scope(engine());
229 QV4::ScopedContext ctxt(scope, findContext(frame: frameNr));
230 while (ctxt) {
231 if (QV4::CallContext *cCtxt = ctxt->asCallContext()) {
232 if (cCtxt->d()->activation)
233 break;
234 }
235 ctxt = ctxt->d()->outer;
236 }
237
238 if (ctxt) {
239 QV4::ScopedValue o(scope, ctxt->d()->activation);
240 frame[QLatin1String("receiver")] = toRef(ref: addValueRef(value: o));
241 }
242
243 // Only type and index are used by Qt Creator, so we keep it easy:
244 QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes = getScopeTypes(frame: frameNr);
245 for (int i = 0, ei = scopeTypes.size(); i != ei; ++i) {
246 int type = encodeScopeType(scopeType: scopeTypes[i]);
247 if (type == -1)
248 continue;
249
250 QJsonObject scope;
251 scope[QLatin1String("index")] = i;
252 scope[QLatin1String("type")] = type;
253 scopes.push_back(t: scope);
254 }
255
256 frame[QLatin1String("scopes")] = scopes;
257
258 return frame;
259}
260
261void QV4DataCollector::clear()
262{
263 m_values.set(engine: engine(), obj: engine()->newArrayObject());
264}
265
266QV4DataCollector::Ref QV4DataCollector::addRef(QV4::Value value, bool deduplicate)
267{
268 class ExceptionStateSaver
269 {
270 quint8 *hasExceptionLoc;
271 quint8 hadException;
272
273 public:
274 ExceptionStateSaver(QV4::ExecutionEngine *engine)
275 : hasExceptionLoc(&engine->hasException)
276 , hadException(false)
277 { std::swap(a&: *hasExceptionLoc, b&: hadException); }
278
279 ~ExceptionStateSaver()
280 { std::swap(a&: *hasExceptionLoc, b&: hadException); }
281 };
282
283 // if we wouldn't do this, the put won't work.
284 ExceptionStateSaver resetExceptionState(engine());
285 QV4::Scope scope(engine());
286 QV4::ScopedObject array(scope, m_values.value());
287 if (deduplicate) {
288 for (Ref i = 0; i < array->getLength(); ++i) {
289 if (array->get(idx: i) == value.rawValue())
290 return i;
291 }
292 }
293 Ref ref = array->getLength();
294 array->put(idx: ref, v: value);
295 Q_ASSERT(array->getLength() - 1 == ref);
296 return ref;
297}
298
299QV4::ReturnedValue QV4DataCollector::getValue(Ref ref)
300{
301 QV4::Scope scope(engine());
302 QV4::ScopedObject array(scope, m_values.value());
303 Q_ASSERT(ref < array->getLength());
304 return array->get(idx: ref, hasProperty: nullptr);
305}
306
307class CapturePreventer
308{
309public:
310 CapturePreventer(QV4::ExecutionEngine *engine)
311 {
312 if (QQmlEngine *e = engine->qmlEngine()) {
313 m_engine = QQmlEnginePrivate::get(e);
314 m_capture = m_engine->propertyCapture;
315 m_engine->propertyCapture = nullptr;
316 }
317 }
318
319 ~CapturePreventer()
320 {
321 if (m_engine && m_capture) {
322 Q_ASSERT(!m_engine->propertyCapture);
323 m_engine->propertyCapture = m_capture;
324 }
325 }
326
327private:
328 QQmlEnginePrivate *m_engine = nullptr;
329 QQmlPropertyCapture *m_capture = nullptr;
330};
331
332QJsonArray QV4DataCollector::collectProperties(const QV4::Object *object)
333{
334 CapturePreventer capturePreventer(engine());
335 Q_UNUSED(capturePreventer);
336
337 QJsonArray res;
338
339 QV4::Scope scope(engine());
340 QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
341 QV4::ScopedValue name(scope);
342 QV4::ScopedValue value(scope);
343 while (true) {
344 QV4::Value v;
345 name = it.nextPropertyNameAsString(value: &v);
346 if (name->isNull())
347 break;
348 QString key = name->toQStringNoThrow();
349 value = v;
350 res.append(value: collectAsJson(name: key, value));
351 }
352
353 return res;
354}
355
356QJsonObject QV4DataCollector::collectAsJson(const QString &name, const QV4::ScopedValue &value)
357{
358 QJsonObject dict;
359 if (!name.isNull())
360 dict.insert(QStringLiteral("name"), value: name);
361 if (value->isManaged() && !value->isString()) {
362 Ref ref = addRef(value);
363 dict.insert(QStringLiteral("ref"), value: qint64(ref));
364 }
365
366 collectProperty(value, engine: engine(), dict);
367 return dict;
368}
369
370QT_END_NAMESPACE
371

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