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(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, 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"), 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(valueKey, QJsonValue::Undefined);
138 return nullptr;
139 case QV4::Value::Null_Type:
140 dict.insert(valueKey, QJsonValue::Null);
141 return nullptr;
142 case QV4::Value::Boolean_Type:
143 dict.insert(valueKey, value->booleanValue());
144 return nullptr;
145 case QV4::Value::Managed_Type:
146 if (const QV4::String *s = value->as<QV4::String>()) {
147 dict.insert(valueKey, 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(valueKey, 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(nullptr, &attrs);
160 if (!name->isValid())
161 break;
162 ++numProperties;
163 }
164 dict.insert(valueKey, numProperties);
165 return o;
166 } else {
167 Q_UNREACHABLE();
168 }
169 return nullptr;
170 case QV4::Value::Integer_Type:
171 dict.insert(valueKey, value->integerValue());
172 return nullptr;
173 default: {// double
174 const double val = value->doubleValue();
175 if (qIsFinite(val))
176 dict.insert(valueKey, val);
177 else if (qIsNaN(val))
178 dict.insert(valueKey, QStringLiteral("NaN"));
179 else if (val < 0)
180 dict.insert(valueKey, QStringLiteral("-Infinity"));
181 else
182 dict.insert(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"), qint64(ref));
193 QV4::Scope scope(engine());
194 QV4::ScopedValue value(scope, getValue(ref));
195
196 const QV4::Object *object = collectProperty(value, engine(), dict);
197 if (object)
198 dict.insert(QStringLiteral("properties"), 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(findContext(frameNr), 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 QStringList names;
221 Refs collectedRefs;
222
223 QV4::ScopedValue v(scope);
224 QV4::Heap::InternalClass *ic = ctxt->internalClass();
225 for (uint i = 0; i < ic->size; ++i) {
226 QString name = ic->keyAt(i);
227 names.append(name);
228 v = static_cast<QV4::Heap::CallContext *>(ctxt->d())->locals[i];
229 collectedRefs.append(addValueRef(v));
230 }
231
232 Q_ASSERT(names.size() == collectedRefs.size());
233 QV4::ScopedString propName(scope);
234 for (int i = 0, ei = collectedRefs.size(); i != ei; ++i) {
235 propName = engine()->newString(names.at(i));
236 scopeObject->put(propName, (v = getValue(collectedRefs.at(i))));
237 }
238 }
239
240 *dict = lookupRef(addRef(scopeObject));
241
242 return true;
243}
244
245QJsonObject toRef(QV4DataCollector::Ref ref) {
246 QJsonObject dict;
247 dict.insert(QStringLiteral("ref"), qint64(ref));
248 return dict;
249}
250
251QJsonObject QV4DataCollector::buildFrame(const QV4::StackFrame &stackFrame, int frameNr)
252{
253 QJsonObject frame;
254 frame[QLatin1String("index")] = frameNr;
255 frame[QLatin1String("debuggerFrame")] = false;
256 frame[QLatin1String("func")] = stackFrame.function;
257 frame[QLatin1String("script")] = stackFrame.source;
258 frame[QLatin1String("line")] = stackFrame.line - 1;
259 if (stackFrame.column >= 0)
260 frame[QLatin1String("column")] = stackFrame.column;
261
262 QJsonArray scopes;
263 QV4::Scope scope(engine());
264 QV4::ScopedContext ctxt(scope, findContext(frameNr));
265 while (ctxt) {
266 if (QV4::CallContext *cCtxt = ctxt->asCallContext()) {
267 if (cCtxt->d()->activation)
268 break;
269 }
270 ctxt = ctxt->d()->outer;
271 }
272
273 if (ctxt) {
274 QV4::ScopedValue o(scope, ctxt->d()->activation);
275 frame[QLatin1String("receiver")] = toRef(addValueRef(o));
276 }
277
278 // Only type and index are used by Qt Creator, so we keep it easy:
279 QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes = getScopeTypes(frameNr);
280 for (int i = 0, ei = scopeTypes.count(); i != ei; ++i) {
281 int type = encodeScopeType(scopeTypes[i]);
282 if (type == -1)
283 continue;
284
285 QJsonObject scope;
286 scope[QLatin1String("index")] = i;
287 scope[QLatin1String("type")] = type;
288 scopes.push_back(scope);
289 }
290
291 frame[QLatin1String("scopes")] = scopes;
292
293 return frame;
294}
295
296void QV4DataCollector::clear()
297{
298 m_values.set(engine(), engine()->newArrayObject());
299}
300
301QV4DataCollector::Ref QV4DataCollector::addRef(QV4::Value value, bool deduplicate)
302{
303 class ExceptionStateSaver
304 {
305 quint8 *hasExceptionLoc;
306 quint8 hadException;
307
308 public:
309 ExceptionStateSaver(QV4::ExecutionEngine *engine)
310 : hasExceptionLoc(&engine->hasException)
311 , hadException(false)
312 { std::swap(*hasExceptionLoc, hadException); }
313
314 ~ExceptionStateSaver()
315 { std::swap(*hasExceptionLoc, hadException); }
316 };
317
318 // if we wouldn't do this, the put won't work.
319 ExceptionStateSaver resetExceptionState(engine());
320 QV4::Scope scope(engine());
321 QV4::ScopedObject array(scope, m_values.value());
322 if (deduplicate) {
323 for (Ref i = 0; i < array->getLength(); ++i) {
324 if (array->get(i) == value.rawValue())
325 return i;
326 }
327 }
328 Ref ref = array->getLength();
329 array->put(ref, value);
330 Q_ASSERT(array->getLength() - 1 == ref);
331 return ref;
332}
333
334QV4::ReturnedValue QV4DataCollector::getValue(Ref ref)
335{
336 QV4::Scope scope(engine());
337 QV4::ScopedObject array(scope, m_values.value());
338 Q_ASSERT(ref < array->getLength());
339 return array->get(ref, nullptr);
340}
341
342class CapturePreventer
343{
344public:
345 CapturePreventer(QV4::ExecutionEngine *engine)
346 {
347 if (QQmlEngine *e = engine->qmlEngine()) {
348 m_engine = QQmlEnginePrivate::get(e);
349 m_capture = m_engine->propertyCapture;
350 m_engine->propertyCapture = nullptr;
351 }
352 }
353
354 ~CapturePreventer()
355 {
356 if (m_engine && m_capture) {
357 Q_ASSERT(!m_engine->propertyCapture);
358 m_engine->propertyCapture = m_capture;
359 }
360 }
361
362private:
363 QQmlEnginePrivate *m_engine = nullptr;
364 QQmlPropertyCapture *m_capture = nullptr;
365};
366
367QJsonArray QV4DataCollector::collectProperties(const QV4::Object *object)
368{
369 CapturePreventer capturePreventer(engine());
370 Q_UNUSED(capturePreventer);
371
372 QJsonArray res;
373
374 QV4::Scope scope(engine());
375 QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
376 QV4::ScopedValue name(scope);
377 QV4::ScopedValue value(scope);
378 while (true) {
379 QV4::Value v;
380 name = it.nextPropertyNameAsString(&v);
381 if (name->isNull())
382 break;
383 QString key = name->toQStringNoThrow();
384 value = v;
385 res.append(collectAsJson(key, value));
386 }
387
388 return res;
389}
390
391QJsonObject QV4DataCollector::collectAsJson(const QString &name, const QV4::ScopedValue &value)
392{
393 QJsonObject dict;
394 if (!name.isNull())
395 dict.insert(QStringLiteral("name"), name);
396 if (value->isManaged() && !value->isString()) {
397 Ref ref = addRef(value);
398 dict.insert(QStringLiteral("ref"), qint64(ref));
399 }
400
401 collectProperty(value, engine(), dict);
402 return dict;
403}
404
405QT_END_NAMESPACE
406

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