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

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