Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #include "qv4datacollector.h"
++ #include "qv4debugger.h"
++ #include "qv4debugjob.h"
++
++ #include <private/qqmlcontext_p.h>
++ #include <private/qqmlengine_p.h>
++ #include <private/qv4identifierhash_p.h>
++ #include <private/qv4identifiertable_p.h>
++ #include <private/qv4objectiterator_p.h>
++ #include <private/qv4runtime_p.h>
++ #include <private/qv4script_p.h>
++ #include <private/qv4stackframe_p.h>
++ #include <private/qv4string_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->parentFrame();
++ }
++ 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;
++ }
++
++ QList<QV4::Heap::ExecutionContext::ContextType> QV4DataCollector::getScopeTypes(int frame)
++ {
++ QList<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 ||
++ ctxt->d()->type == QV4::Heap::ExecutionContext::Type_BlockContext) {
++ QStringList names;
++ Refs collectedRefs;
++
++ QV4::ScopedValue v(scope);
++ QV4::Heap::InternalClass *ic = ctxt->internalClass();
++ for (uint i = 0; i < ic->size; ++i) {
++ QV4::ScopedValue stringOrSymbol(scope, ic->keyAt(i));
++ QV4::ScopedString propName(scope, stringOrSymbol->toString(scope.engine));
++ names.append(propName->toQString());
++ v = ctxt->getProperty(propName);
++ 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")] = qAbs(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:
++ QList<QV4::Heap::ExecutionContext::ContextType> scopeTypes = getScopeTypes(frameNr);
++ for (int i = 0, ei = scopeTypes.size(); 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