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 | |
21 | QT_BEGIN_NAMESPACE |
22 | |
23 | QV4::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 | |
33 | QV4::Heap::ExecutionContext *QV4DataCollector::findContext(int frame) |
34 | { |
35 | QV4::CppStackFrame *f = findFrame(frame); |
36 | |
37 | return f ? f->context()->d() : nullptr; |
38 | } |
39 | |
40 | QV4::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 | |
48 | QVector<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 | |
60 | int 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 | |
77 | QV4DataCollector::QV4DataCollector(QV4::ExecutionEngine *engine) |
78 | : m_engine(engine) |
79 | { |
80 | m_values.set(engine, obj: engine->newArrayObject()); |
81 | } |
82 | |
83 | QV4DataCollector::Ref QV4DataCollector::addValueRef(const QV4::ScopedValue &value) |
84 | { |
85 | return addRef(value); |
86 | } |
87 | |
88 | const 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 | |
152 | QJsonObject 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 | |
167 | bool 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 | |
174 | bool 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 | |
210 | QJsonObject toRef(QV4DataCollector::Ref ref) { |
211 | QJsonObject dict; |
212 | dict.insert(QStringLiteral("ref" ), value: qint64(ref)); |
213 | return dict; |
214 | } |
215 | |
216 | QJsonObject 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 | |
261 | void QV4DataCollector::clear() |
262 | { |
263 | m_values.set(engine: engine(), obj: engine()->newArrayObject()); |
264 | } |
265 | |
266 | QV4DataCollector::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 | |
299 | QV4::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 | |
307 | class CapturePreventer |
308 | { |
309 | public: |
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 | |
327 | private: |
328 | QQmlEnginePrivate *m_engine = nullptr; |
329 | QQmlPropertyCapture *m_capture = nullptr; |
330 | }; |
331 | |
332 | QJsonArray 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 | |
356 | QJsonObject 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 | |
370 | QT_END_NAMESPACE |
371 | |