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