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 QV4::ScopedValue stringOrSymbol(scope, ic->keyAt(index: i));
192 QV4::ScopedString propName(scope, stringOrSymbol->toString(e: scope.engine));
193 names.append(t: propName->toQString());
194 v = ctxt->getProperty(name: propName);
195 collectedRefs.append(t: addValueRef(value: v));
196 }
197
198 Q_ASSERT(names.size() == collectedRefs.size());
199 QV4::ScopedString propName(scope);
200 for (int i = 0, ei = collectedRefs.size(); i != ei; ++i) {
201 propName = engine()->newString(s: names.at(i));
202 scopeObject->put(name: propName, v: (v = getValue(ref: collectedRefs.at(i))));
203 }
204 }
205
206 *dict = lookupRef(ref: addRef(value: scopeObject));
207
208 return true;
209}
210
211QJsonObject toRef(QV4DataCollector::Ref ref) {
212 QJsonObject dict;
213 dict.insert(QStringLiteral("ref"), value: qint64(ref));
214 return dict;
215}
216
217QJsonObject QV4DataCollector::buildFrame(const QV4::StackFrame &stackFrame, int frameNr)
218{
219 QJsonObject frame;
220 frame[QLatin1String("index")] = frameNr;
221 frame[QLatin1String("debuggerFrame")] = false;
222 frame[QLatin1String("func")] = stackFrame.function;
223 frame[QLatin1String("script")] = stackFrame.source;
224 frame[QLatin1String("line")] = qAbs(t: stackFrame.line) - 1;
225 if (stackFrame.column >= 0)
226 frame[QLatin1String("column")] = stackFrame.column;
227
228 QJsonArray scopes;
229 QV4::Scope scope(engine());
230 QV4::ScopedContext ctxt(scope, findContext(frame: frameNr));
231 while (ctxt) {
232 if (QV4::CallContext *cCtxt = ctxt->asCallContext()) {
233 if (cCtxt->d()->activation)
234 break;
235 }
236 ctxt = ctxt->d()->outer;
237 }
238
239 if (ctxt) {
240 QV4::ScopedValue o(scope, ctxt->d()->activation);
241 frame[QLatin1String("receiver")] = toRef(ref: addValueRef(value: o));
242 }
243
244 // Only type and index are used by Qt Creator, so we keep it easy:
245 QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes = getScopeTypes(frame: frameNr);
246 for (int i = 0, ei = scopeTypes.size(); i != ei; ++i) {
247 int type = encodeScopeType(scopeType: scopeTypes[i]);
248 if (type == -1)
249 continue;
250
251 QJsonObject scope;
252 scope[QLatin1String("index")] = i;
253 scope[QLatin1String("type")] = type;
254 scopes.push_back(t: scope);
255 }
256
257 frame[QLatin1String("scopes")] = scopes;
258
259 return frame;
260}
261
262void QV4DataCollector::clear()
263{
264 m_values.set(engine: engine(), obj: engine()->newArrayObject());
265}
266
267QV4DataCollector::Ref QV4DataCollector::addRef(QV4::Value value, bool deduplicate)
268{
269 class ExceptionStateSaver
270 {
271 quint8 *hasExceptionLoc;
272 quint8 hadException;
273
274 public:
275 ExceptionStateSaver(QV4::ExecutionEngine *engine)
276 : hasExceptionLoc(&engine->hasException)
277 , hadException(false)
278 { std::swap(a&: *hasExceptionLoc, b&: hadException); }
279
280 ~ExceptionStateSaver()
281 { std::swap(a&: *hasExceptionLoc, b&: hadException); }
282 };
283
284 // if we wouldn't do this, the put won't work.
285 ExceptionStateSaver resetExceptionState(engine());
286 QV4::Scope scope(engine());
287 QV4::ScopedObject array(scope, m_values.value());
288 if (deduplicate) {
289 for (Ref i = 0; i < array->getLength(); ++i) {
290 if (array->get(idx: i) == value.rawValue())
291 return i;
292 }
293 }
294 Ref ref = array->getLength();
295 array->put(idx: ref, v: value);
296 Q_ASSERT(array->getLength() - 1 == ref);
297 return ref;
298}
299
300QV4::ReturnedValue QV4DataCollector::getValue(Ref ref)
301{
302 QV4::Scope scope(engine());
303 QV4::ScopedObject array(scope, m_values.value());
304 Q_ASSERT(ref < array->getLength());
305 return array->get(idx: ref, hasProperty: nullptr);
306}
307
308class CapturePreventer
309{
310public:
311 CapturePreventer(QV4::ExecutionEngine *engine)
312 {
313 if (QQmlEngine *e = engine->qmlEngine()) {
314 m_engine = QQmlEnginePrivate::get(e);
315 m_capture = m_engine->propertyCapture;
316 m_engine->propertyCapture = nullptr;
317 }
318 }
319
320 ~CapturePreventer()
321 {
322 if (m_engine && m_capture) {
323 Q_ASSERT(!m_engine->propertyCapture);
324 m_engine->propertyCapture = m_capture;
325 }
326 }
327
328private:
329 QQmlEnginePrivate *m_engine = nullptr;
330 QQmlPropertyCapture *m_capture = nullptr;
331};
332
333QJsonArray QV4DataCollector::collectProperties(const QV4::Object *object)
334{
335 CapturePreventer capturePreventer(engine());
336 Q_UNUSED(capturePreventer);
337
338 QJsonArray res;
339
340 QV4::Scope scope(engine());
341 QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
342 QV4::ScopedValue name(scope);
343 QV4::ScopedValue value(scope);
344 while (true) {
345 QV4::Value v;
346 name = it.nextPropertyNameAsString(value: &v);
347 if (name->isNull())
348 break;
349 QString key = name->toQStringNoThrow();
350 value = v;
351 res.append(value: collectAsJson(name: key, value));
352 }
353
354 return res;
355}
356
357QJsonObject QV4DataCollector::collectAsJson(const QString &name, const QV4::ScopedValue &value)
358{
359 QJsonObject dict;
360 if (!name.isNull())
361 dict.insert(QStringLiteral("name"), value: name);
362 if (value->isManaged() && !value->isString()) {
363 Ref ref = addRef(value);
364 dict.insert(QStringLiteral("ref"), value: qint64(ref));
365 }
366
367 collectProperty(value, engine: engine(), dict);
368 return dict;
369}
370
371QT_END_NAMESPACE
372

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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