| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQml module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qv4datacollector.h" |
| #include "qv4debugger.h" |
| #include "qv4debugjob.h" |
| |
| #include <private/qv4script_p.h> |
| #include <private/qv4string_p.h> |
| #include <private/qv4objectiterator_p.h> |
| #include <private/qv4identifier_p.h> |
| #include <private/qv4runtime_p.h> |
| #include <private/qv4identifiertable_p.h> |
| |
| #include <private/qqmlcontext_p.h> |
| #include <private/qqmlengine_p.h> |
| |
| #include <QtCore/qjsonarray.h> |
| #include <QtCore/qjsonobject.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| QV4::CppStackFrame *QV4DataCollector::findFrame(int frame) |
| { |
| QV4::CppStackFrame *f = engine()->currentStackFrame; |
| while (f && frame) { |
| --frame; |
| f = f->parent; |
| } |
| return f; |
| } |
| |
| QV4::Heap::ExecutionContext *QV4DataCollector::findContext(int frame) |
| { |
| QV4::CppStackFrame *f = findFrame(frame); |
| |
| return f ? f->context()->d() : nullptr; |
| } |
| |
| QV4::Heap::ExecutionContext *QV4DataCollector::findScope(QV4::Heap::ExecutionContext *ctx, int scope) |
| { |
| for (; scope > 0 && ctx; --scope) |
| ctx = ctx->outer; |
| |
| return ctx; |
| } |
| |
| QVector<QV4::Heap::ExecutionContext::ContextType> QV4DataCollector::getScopeTypes(int frame) |
| { |
| QVector<QV4::Heap::ExecutionContext::ContextType> types; |
| |
| QV4::Heap::ExecutionContext *it = findFrame(frame)->context()->d(); |
| |
| for (; it; it = it->outer) |
| types.append(QV4::Heap::ExecutionContext::ContextType(it->type)); |
| |
| return types; |
| } |
| |
| int QV4DataCollector::encodeScopeType(QV4::Heap::ExecutionContext::ContextType scopeType) |
| { |
| switch (scopeType) { |
| case QV4::Heap::ExecutionContext::Type_GlobalContext: |
| break; |
| case QV4::Heap::ExecutionContext::Type_WithContext: |
| return 2; |
| case QV4::Heap::ExecutionContext::Type_CallContext: |
| return 1; |
| case QV4::Heap::ExecutionContext::Type_QmlContext: |
| return 3; |
| case QV4::Heap::ExecutionContext::Type_BlockContext: |
| return 4; |
| } |
| return 0; |
| } |
| |
| QV4DataCollector::QV4DataCollector(QV4::ExecutionEngine *engine) |
| : m_engine(engine) |
| { |
| m_values.set(engine, engine->newArrayObject()); |
| } |
| |
| QV4DataCollector::Ref QV4DataCollector::addValueRef(const QV4::ScopedValue &value) |
| { |
| return addRef(value); |
| } |
| |
| const QV4::Object *collectProperty(const QV4::ScopedValue &value, QV4::ExecutionEngine *engine, |
| QJsonObject &dict) |
| { |
| QV4::Scope scope(engine); |
| QV4::ScopedValue typeString(scope, QV4::Runtime::TypeofValue::call(engine, value)); |
| dict.insert(QStringLiteral("type"), typeString->toQStringNoThrow()); |
| |
| const QLatin1String valueKey("value"); |
| switch (value->type()) { |
| case QV4::Value::Empty_Type: |
| Q_ASSERT(!"empty Value encountered"); |
| return nullptr; |
| case QV4::Value::Undefined_Type: |
| dict.insert(valueKey, QJsonValue::Undefined); |
| return nullptr; |
| case QV4::Value::Null_Type: |
| dict.insert(valueKey, QJsonValue::Null); |
| return nullptr; |
| case QV4::Value::Boolean_Type: |
| dict.insert(valueKey, value->booleanValue()); |
| return nullptr; |
| case QV4::Value::Managed_Type: |
| if (const QV4::String *s = value->as<QV4::String>()) { |
| dict.insert(valueKey, s->toQString()); |
| } else if (const QV4::ArrayObject *a = value->as<QV4::ArrayObject>()) { |
| // size of an array is number of its numerical properties; We don't consider free form |
| // object properties here. |
| dict.insert(valueKey, qint64(a->getLength())); |
| return a; |
| } else if (const QV4::Object *o = value->as<QV4::Object>()) { |
| int numProperties = 0; |
| QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly); |
| QV4::PropertyAttributes attrs; |
| QV4::ScopedPropertyKey name(scope); |
| while (true) { |
| name = it.next(nullptr, &attrs); |
| if (!name->isValid()) |
| break; |
| ++numProperties; |
| } |
| dict.insert(valueKey, numProperties); |
| return o; |
| } else { |
| Q_UNREACHABLE(); |
| } |
| return nullptr; |
| case QV4::Value::Integer_Type: |
| dict.insert(valueKey, value->integerValue()); |
| return nullptr; |
| default: {// double |
| const double val = value->doubleValue(); |
| if (qIsFinite(val)) |
| dict.insert(valueKey, val); |
| else if (qIsNaN(val)) |
| dict.insert(valueKey, QStringLiteral("NaN")); |
| else if (val < 0) |
| dict.insert(valueKey, QStringLiteral("-Infinity")); |
| else |
| dict.insert(valueKey, QStringLiteral("Infinity")); |
| return nullptr; |
| } |
| } |
| } |
| |
| QJsonObject QV4DataCollector::lookupRef(Ref ref) |
| { |
| QJsonObject dict; |
| |
| dict.insert(QStringLiteral("handle"), qint64(ref)); |
| QV4::Scope scope(engine()); |
| QV4::ScopedValue value(scope, getValue(ref)); |
| |
| const QV4::Object *object = collectProperty(value, engine(), dict); |
| if (object) |
| dict.insert(QStringLiteral("properties"), collectProperties(object)); |
| |
| return dict; |
| } |
| |
| bool QV4DataCollector::isValidRef(QV4DataCollector::Ref ref) const |
| { |
| QV4::Scope scope(engine()); |
| QV4::ScopedObject array(scope, m_values.value()); |
| return ref < array->getLength(); |
| } |
| |
| bool QV4DataCollector::collectScope(QJsonObject *dict, int frameNr, int scopeNr) |
| { |
| QV4::Scope scope(engine()); |
| |
| QV4::Scoped<QV4::ExecutionContext> ctxt(scope, findScope(findContext(frameNr), scopeNr)); |
| if (!ctxt) |
| return false; |
| |
| QV4::ScopedObject scopeObject(scope, engine()->newObject()); |
| if (ctxt->d()->type == QV4::Heap::ExecutionContext::Type_CallContext) { |
| QStringList names; |
| Refs collectedRefs; |
| |
| QV4::ScopedValue v(scope); |
| QV4::Heap::InternalClass *ic = ctxt->internalClass(); |
| for (uint i = 0; i < ic->size; ++i) { |
| QString name = ic->keyAt(i); |
| names.append(name); |
| v = static_cast<QV4::Heap::CallContext *>(ctxt->d())->locals[i]; |
| collectedRefs.append(addValueRef(v)); |
| } |
| |
| Q_ASSERT(names.size() == collectedRefs.size()); |
| QV4::ScopedString propName(scope); |
| for (int i = 0, ei = collectedRefs.size(); i != ei; ++i) { |
| propName = engine()->newString(names.at(i)); |
| scopeObject->put(propName, (v = getValue(collectedRefs.at(i)))); |
| } |
| } |
| |
| *dict = lookupRef(addRef(scopeObject)); |
| |
| return true; |
| } |
| |
| QJsonObject toRef(QV4DataCollector::Ref ref) { |
| QJsonObject dict; |
| dict.insert(QStringLiteral("ref"), qint64(ref)); |
| return dict; |
| } |
| |
| QJsonObject QV4DataCollector::buildFrame(const QV4::StackFrame &stackFrame, int frameNr) |
| { |
| QJsonObject frame; |
| frame[QLatin1String("index")] = frameNr; |
| frame[QLatin1String("debuggerFrame")] = false; |
| frame[QLatin1String("func")] = stackFrame.function; |
| frame[QLatin1String("script")] = stackFrame.source; |
| frame[QLatin1String("line")] = stackFrame.line - 1; |
| if (stackFrame.column >= 0) |
| frame[QLatin1String("column")] = stackFrame.column; |
| |
| QJsonArray scopes; |
| QV4::Scope scope(engine()); |
| QV4::ScopedContext ctxt(scope, findContext(frameNr)); |
| while (ctxt) { |
| if (QV4::CallContext *cCtxt = ctxt->asCallContext()) { |
| if (cCtxt->d()->activation) |
| break; |
| } |
| ctxt = ctxt->d()->outer; |
| } |
| |
| if (ctxt) { |
| QV4::ScopedValue o(scope, ctxt->d()->activation); |
| frame[QLatin1String("receiver")] = toRef(addValueRef(o)); |
| } |
| |
| // Only type and index are used by Qt Creator, so we keep it easy: |
| QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes = getScopeTypes(frameNr); |
| for (int i = 0, ei = scopeTypes.count(); i != ei; ++i) { |
| int type = encodeScopeType(scopeTypes[i]); |
| if (type == -1) |
| continue; |
| |
| QJsonObject scope; |
| scope[QLatin1String("index")] = i; |
| scope[QLatin1String("type")] = type; |
| scopes.push_back(scope); |
| } |
| |
| frame[QLatin1String("scopes")] = scopes; |
| |
| return frame; |
| } |
| |
| void QV4DataCollector::clear() |
| { |
| m_values.set(engine(), engine()->newArrayObject()); |
| } |
| |
| QV4DataCollector::Ref QV4DataCollector::addRef(QV4::Value value, bool deduplicate) |
| { |
| class ExceptionStateSaver |
| { |
| quint8 *hasExceptionLoc; |
| quint8 hadException; |
| |
| public: |
| ExceptionStateSaver(QV4::ExecutionEngine *engine) |
| : hasExceptionLoc(&engine->hasException) |
| , hadException(false) |
| { std::swap(*hasExceptionLoc, hadException); } |
| |
| ~ExceptionStateSaver() |
| { std::swap(*hasExceptionLoc, hadException); } |
| }; |
| |
| // if we wouldn't do this, the put won't work. |
| ExceptionStateSaver resetExceptionState(engine()); |
| QV4::Scope scope(engine()); |
| QV4::ScopedObject array(scope, m_values.value()); |
| if (deduplicate) { |
| for (Ref i = 0; i < array->getLength(); ++i) { |
| if (array->get(i) == value.rawValue()) |
| return i; |
| } |
| } |
| Ref ref = array->getLength(); |
| array->put(ref, value); |
| Q_ASSERT(array->getLength() - 1 == ref); |
| return ref; |
| } |
| |
| QV4::ReturnedValue QV4DataCollector::getValue(Ref ref) |
| { |
| QV4::Scope scope(engine()); |
| QV4::ScopedObject array(scope, m_values.value()); |
| Q_ASSERT(ref < array->getLength()); |
| return array->get(ref, nullptr); |
| } |
| |
| class CapturePreventer |
| { |
| public: |
| CapturePreventer(QV4::ExecutionEngine *engine) |
| { |
| if (QQmlEngine *e = engine->qmlEngine()) { |
| m_engine = QQmlEnginePrivate::get(e); |
| m_capture = m_engine->propertyCapture; |
| m_engine->propertyCapture = nullptr; |
| } |
| } |
| |
| ~CapturePreventer() |
| { |
| if (m_engine && m_capture) { |
| Q_ASSERT(!m_engine->propertyCapture); |
| m_engine->propertyCapture = m_capture; |
| } |
| } |
| |
| private: |
| QQmlEnginePrivate *m_engine = nullptr; |
| QQmlPropertyCapture *m_capture = nullptr; |
| }; |
| |
| QJsonArray QV4DataCollector::collectProperties(const QV4::Object *object) |
| { |
| CapturePreventer capturePreventer(engine()); |
| Q_UNUSED(capturePreventer); |
| |
| QJsonArray res; |
| |
| QV4::Scope scope(engine()); |
| QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly); |
| QV4::ScopedValue name(scope); |
| QV4::ScopedValue value(scope); |
| while (true) { |
| QV4::Value v; |
| name = it.nextPropertyNameAsString(&v); |
| if (name->isNull()) |
| break; |
| QString key = name->toQStringNoThrow(); |
| value = v; |
| res.append(collectAsJson(key, value)); |
| } |
| |
| return res; |
| } |
| |
| QJsonObject QV4DataCollector::collectAsJson(const QString &name, const QV4::ScopedValue &value) |
| { |
| QJsonObject dict; |
| if (!name.isNull()) |
| dict.insert(QStringLiteral("name"), name); |
| if (value->isManaged() && !value->isString()) { |
| Ref ref = addRef(value); |
| dict.insert(QStringLiteral("ref"), qint64(ref)); |
| } |
| |
| collectProperty(value, engine(), dict); |
| return dict; |
| } |
| |
| QT_END_NAMESPACE |