| /**************************************************************************** |
| ** |
| ** Copyright (C) 2018 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 "qv4proxy_p.h" |
| #include "qv4symbol_p.h" |
| #include "qv4jscall_p.h" |
| #include "qv4objectproto_p.h" |
| #include "qv4persistent_p.h" |
| #include "qv4objectiterator_p.h" |
| |
| using namespace QV4; |
| |
| DEFINE_OBJECT_VTABLE(ProxyObject); |
| DEFINE_OBJECT_VTABLE(ProxyFunctionObject); |
| |
| void Heap::ProxyObject::init(const QV4::Object *target, const QV4::Object *handler) |
| { |
| Object::init(); |
| ExecutionEngine *e = internalClass->engine; |
| this->target.set(e, target->d()); |
| this->handler.set(e, handler->d()); |
| } |
| |
| void Heap::ProxyFunctionObject::init(const QV4::FunctionObject *target, const QV4::Object *handler) |
| { |
| ExecutionEngine *e = internalClass->engine; |
| FunctionObject::init(e->rootContext()); |
| this->target.set(e, target->d()); |
| this->handler.set(e, handler->d()); |
| |
| if (!target->isConstructor()) |
| jsConstruct = nullptr; |
| } |
| |
| |
| ReturnedValue ProxyObject::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) |
| { |
| Scope scope(m); |
| const ProxyObject *o = static_cast<const ProxyObject *>(m); |
| if (!o->d()->handler) |
| return scope.engine->throwTypeError(); |
| |
| ScopedObject target(scope, o->d()->target); |
| Q_ASSERT(target); |
| ScopedObject handler(scope, o->d()->handler); |
| ScopedValue trap(scope, handler->get(scope.engine->id_get())); |
| if (scope.hasException()) |
| return Encode::undefined(); |
| if (trap->isNullOrUndefined()) |
| return target->get(id, receiver, hasProperty); |
| if (!trap->isFunctionObject()) |
| return scope.engine->throwTypeError(); |
| if (hasProperty) |
| *hasProperty = true; |
| |
| JSCallData cdata(scope, 3, nullptr, handler); |
| cdata.args[0] = target; |
| cdata.args[1] = id.toStringOrSymbol(scope.engine); |
| cdata.args[2] = *receiver; |
| |
| ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); |
| if (scope.engine->hasException) |
| return Encode::undefined(); |
| ScopedProperty targetDesc(scope); |
| PropertyAttributes attributes = target->getOwnProperty(id, targetDesc); |
| if (attributes != Attr_Invalid && !attributes.isConfigurable()) { |
| if (attributes.isData() && !attributes.isWritable()) { |
| if (!trapResult->sameValue(targetDesc->value)) |
| return scope.engine->throwTypeError(); |
| } |
| if (attributes.isAccessor() && targetDesc->value.isUndefined()) { |
| if (!trapResult->isUndefined()) |
| return scope.engine->throwTypeError(); |
| } |
| } |
| return trapResult->asReturnedValue(); |
| } |
| |
| bool ProxyObject::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) |
| { |
| Scope scope(m); |
| const ProxyObject *o = static_cast<const ProxyObject *>(m); |
| if (!o->d()->handler) |
| return scope.engine->throwTypeError(); |
| |
| ScopedObject target(scope, o->d()->target); |
| Q_ASSERT(target); |
| ScopedObject handler(scope, o->d()->handler); |
| ScopedValue trap(scope, handler->get(scope.engine->id_set())); |
| if (scope.hasException()) |
| return Encode::undefined(); |
| if (trap->isNullOrUndefined()) |
| return target->put(id, value, receiver); |
| if (!trap->isFunctionObject()) |
| return scope.engine->throwTypeError(); |
| |
| JSCallData cdata(scope, 4, nullptr, handler); |
| cdata.args[0] = target; |
| cdata.args[1] = id.toStringOrSymbol(scope.engine); |
| cdata.args[2] = value; |
| cdata.args[3] = *receiver; |
| |
| ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); |
| if (scope.engine->hasException || !trapResult->toBoolean()) |
| return false; |
| ScopedProperty targetDesc(scope); |
| PropertyAttributes attributes = target->getOwnProperty(id, targetDesc); |
| if (attributes != Attr_Invalid && !attributes.isConfigurable()) { |
| if (attributes.isData() && !attributes.isWritable()) { |
| if (!value.sameValue(targetDesc->value)) |
| return scope.engine->throwTypeError(); |
| } |
| if (attributes.isAccessor() && targetDesc->set.isUndefined()) |
| return scope.engine->throwTypeError(); |
| } |
| return true; |
| } |
| |
| bool ProxyObject::virtualDeleteProperty(Managed *m, PropertyKey id) |
| { |
| Scope scope(m); |
| const ProxyObject *o = static_cast<const ProxyObject *>(m); |
| if (!o->d()->handler) |
| return scope.engine->throwTypeError(); |
| |
| ScopedObject target(scope, o->d()->target); |
| Q_ASSERT(target); |
| ScopedObject handler(scope, o->d()->handler); |
| ScopedString deleteProp(scope, scope.engine->newString(QStringLiteral("deleteProperty"))); |
| ScopedValue trap(scope, handler->get(deleteProp)); |
| if (scope.hasException()) |
| return Encode::undefined(); |
| if (trap->isNullOrUndefined()) |
| return target->deleteProperty(id); |
| if (!trap->isFunctionObject()) |
| return scope.engine->throwTypeError(); |
| |
| JSCallData cdata(scope, 3, nullptr, handler); |
| cdata.args[0] = target; |
| cdata.args[1] = id.toStringOrSymbol(scope.engine); |
| cdata.args[2] = o->d(); // ### fix receiver handling |
| |
| ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); |
| if (scope.engine->hasException || !trapResult->toBoolean()) |
| return false; |
| ScopedProperty targetDesc(scope); |
| PropertyAttributes attributes = target->getOwnProperty(id, targetDesc); |
| if (attributes == Attr_Invalid) |
| return true; |
| if (!attributes.isConfigurable()) |
| return scope.engine->throwTypeError(); |
| return true; |
| } |
| |
| bool ProxyObject::virtualHasProperty(const Managed *m, PropertyKey id) |
| { |
| Scope scope(m); |
| const ProxyObject *o = static_cast<const ProxyObject *>(m); |
| if (!o->d()->handler) |
| return scope.engine->throwTypeError(); |
| |
| ScopedObject target(scope, o->d()->target); |
| Q_ASSERT(target); |
| ScopedObject handler(scope, o->d()->handler); |
| ScopedString hasProp(scope, scope.engine->newString(QStringLiteral("has"))); |
| ScopedValue trap(scope, handler->get(hasProp)); |
| if (scope.hasException()) |
| return Encode::undefined(); |
| if (trap->isNullOrUndefined()) |
| return target->hasProperty(id); |
| if (!trap->isFunctionObject()) |
| return scope.engine->throwTypeError(); |
| |
| JSCallData cdata(scope, 2, nullptr, handler); |
| cdata.args[0] = target; |
| cdata.args[1] = id.isArrayIndex() ? Value::fromUInt32(id.asArrayIndex()).toString(scope.engine) : id.asStringOrSymbol(); |
| |
| ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); |
| if (scope.engine->hasException) |
| return false; |
| bool result = trapResult->toBoolean(); |
| if (!result) { |
| ScopedProperty targetDesc(scope); |
| PropertyAttributes attributes = target->getOwnProperty(id, targetDesc); |
| if (attributes != Attr_Invalid) { |
| if (!attributes.isConfigurable() || !target->isExtensible()) |
| return scope.engine->throwTypeError(); |
| } |
| } |
| return result; |
| } |
| |
| PropertyAttributes ProxyObject::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) |
| { |
| Scope scope(m); |
| const ProxyObject *o = static_cast<const ProxyObject *>(m); |
| if (!o->d()->handler) { |
| scope.engine->throwTypeError(); |
| return Attr_Invalid; |
| } |
| |
| ScopedObject target(scope, o->d()->target); |
| Q_ASSERT(target); |
| ScopedObject handler(scope, o->d()->handler); |
| ScopedString deleteProp(scope, scope.engine->newString(QStringLiteral("getOwnPropertyDescriptor"))); |
| ScopedValue trap(scope, handler->get(deleteProp)); |
| if (scope.hasException()) |
| return Attr_Invalid; |
| if (trap->isNullOrUndefined()) |
| return target->getOwnProperty(id, p); |
| if (!trap->isFunctionObject()) { |
| scope.engine->throwTypeError(); |
| return Attr_Invalid; |
| } |
| |
| JSCallData cdata(scope, 2, nullptr, handler); |
| cdata.args[0] = target; |
| cdata.args[1] = id.isArrayIndex() ? Value::fromUInt32(id.asArrayIndex()).toString(scope.engine) : id.asStringOrSymbol(); |
| |
| ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); |
| if (scope.engine->hasException) |
| return Attr_Invalid; |
| if (!trapResult->isObject() && !trapResult->isUndefined()) { |
| scope.engine->throwTypeError(); |
| return Attr_Invalid; |
| } |
| |
| ScopedProperty targetDesc(scope); |
| PropertyAttributes targetAttributes = target->getOwnProperty(id, targetDesc); |
| if (trapResult->isUndefined()) { |
| p->value = Encode::undefined(); |
| if (targetAttributes == Attr_Invalid) { |
| p->value = Encode::undefined(); |
| return Attr_Invalid; |
| } |
| if (!targetAttributes.isConfigurable() || !target->isExtensible()) { |
| scope.engine->throwTypeError(); |
| return Attr_Invalid; |
| } |
| return Attr_Invalid; |
| } |
| |
| //bool extensibleTarget = target->isExtensible(); |
| ScopedProperty resultDesc(scope); |
| PropertyAttributes resultAttributes; |
| ObjectPrototype::toPropertyDescriptor(scope.engine, trapResult, resultDesc, &resultAttributes); |
| resultDesc->completed(&resultAttributes); |
| |
| if (!targetDesc->isCompatible(targetAttributes, resultDesc, resultAttributes)) { |
| scope.engine->throwTypeError(); |
| return Attr_Invalid; |
| } |
| |
| if (!resultAttributes.isConfigurable()) { |
| if (targetAttributes == Attr_Invalid || targetAttributes.isConfigurable()) { |
| scope.engine->throwTypeError(); |
| return Attr_Invalid; |
| } |
| } |
| |
| p->value = resultDesc->value; |
| p->set = resultDesc->set; |
| return resultAttributes; |
| } |
| |
| bool ProxyObject::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs) |
| { |
| Scope scope(m); |
| const ProxyObject *o = static_cast<const ProxyObject *>(m); |
| if (!o->d()->handler) { |
| scope.engine->throwTypeError(); |
| return false; |
| } |
| |
| ScopedObject target(scope, o->d()->target); |
| Q_ASSERT(target); |
| ScopedObject handler(scope, o->d()->handler); |
| ScopedString prop(scope, scope.engine->newString(QStringLiteral("defineProperty"))); |
| ScopedValue trap(scope, handler->get(prop)); |
| if (scope.hasException()) |
| return false; |
| if (trap->isNullOrUndefined()) |
| return target->defineOwnProperty(id, p, attrs); |
| if (!trap->isFunctionObject()) { |
| scope.engine->throwTypeError(); |
| return false; |
| } |
| |
| JSCallData cdata(scope, 3, nullptr, handler); |
| cdata.args[0] = target; |
| cdata.args[1] = id.isArrayIndex() ? Value::fromUInt32(id.asArrayIndex()).toString(scope.engine) : id.asStringOrSymbol(); |
| cdata.args[2] = ObjectPrototype::fromPropertyDescriptor(scope.engine, p, attrs); |
| |
| ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); |
| bool result = !scope.engine->hasException && trapResult->toBoolean(); |
| if (!result) |
| return false; |
| |
| ScopedProperty targetDesc(scope); |
| PropertyAttributes targetAttributes = target->getOwnProperty(id, targetDesc); |
| bool extensibleTarget = target->isExtensible(); |
| bool settingConfigFalse = attrs.hasConfigurable() && !attrs.isConfigurable(); |
| if (targetAttributes == Attr_Invalid) { |
| if (!extensibleTarget || settingConfigFalse) { |
| scope.engine->throwTypeError(); |
| return false; |
| } |
| } else { |
| if (!targetDesc->isCompatible(targetAttributes, p, attrs)) { |
| scope.engine->throwTypeError(); |
| return false; |
| } |
| if (settingConfigFalse && targetAttributes.isConfigurable()) { |
| scope.engine->throwTypeError(); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ProxyObject::virtualIsExtensible(const Managed *m) |
| { |
| Scope scope(m); |
| const ProxyObject *o = static_cast<const ProxyObject *>(m); |
| if (!o->d()->handler) |
| return scope.engine->throwTypeError(); |
| |
| ScopedObject target(scope, o->d()->target); |
| Q_ASSERT(target); |
| ScopedObject handler(scope, o->d()->handler); |
| ScopedString hasProp(scope, scope.engine->newString(QStringLiteral("isExtensible"))); |
| ScopedValue trap(scope, handler->get(hasProp)); |
| if (scope.hasException()) |
| return Encode::undefined(); |
| if (trap->isNullOrUndefined()) |
| return target->isExtensible(); |
| if (!trap->isFunctionObject()) |
| return scope.engine->throwTypeError(); |
| |
| JSCallData cdata(scope, 1, nullptr, handler); |
| cdata.args[0] = target; |
| |
| ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); |
| if (scope.engine->hasException) |
| return false; |
| bool result = trapResult->toBoolean(); |
| if (result != target->isExtensible()) { |
| scope.engine->throwTypeError(); |
| return false; |
| } |
| return result; |
| } |
| |
| bool ProxyObject::virtualPreventExtensions(Managed *m) |
| { |
| Scope scope(m); |
| const ProxyObject *o = static_cast<const ProxyObject *>(m); |
| if (!o->d()->handler) |
| return scope.engine->throwTypeError(); |
| |
| ScopedObject target(scope, o->d()->target); |
| Q_ASSERT(target); |
| ScopedObject handler(scope, o->d()->handler); |
| ScopedString hasProp(scope, scope.engine->newString(QStringLiteral("preventExtensions"))); |
| ScopedValue trap(scope, handler->get(hasProp)); |
| if (scope.hasException()) |
| return Encode::undefined(); |
| if (trap->isNullOrUndefined()) |
| return target->preventExtensions(); |
| if (!trap->isFunctionObject()) |
| return scope.engine->throwTypeError(); |
| |
| JSCallData cdata(scope, 1, nullptr, handler); |
| cdata.args[0] = target; |
| |
| ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); |
| if (scope.engine->hasException) |
| return false; |
| bool result = trapResult->toBoolean(); |
| if (result && target->isExtensible()) { |
| scope.engine->throwTypeError(); |
| return false; |
| } |
| return result; |
| } |
| |
| Heap::Object *ProxyObject::virtualGetPrototypeOf(const Managed *m) |
| { |
| Scope scope(m); |
| const ProxyObject *o = static_cast<const ProxyObject *>(m); |
| if (!o->d()->handler) { |
| scope.engine->throwTypeError(); |
| return nullptr; |
| } |
| |
| ScopedObject target(scope, o->d()->target); |
| Q_ASSERT(target); |
| ScopedObject handler(scope, o->d()->handler); |
| ScopedString name(scope, scope.engine->newString(QStringLiteral("getPrototypeOf"))); |
| ScopedValue trap(scope, handler->get(name)); |
| if (scope.hasException()) |
| return nullptr; |
| if (trap->isNullOrUndefined()) |
| return target->getPrototypeOf(); |
| if (!trap->isFunctionObject()) { |
| scope.engine->throwTypeError(); |
| return nullptr; |
| } |
| |
| JSCallData cdata(scope, 1, nullptr, handler); |
| cdata.args[0] = target; |
| |
| ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); |
| if (scope.engine->hasException) |
| return nullptr; |
| if (!trapResult->isNull() && !trapResult->isObject()) { |
| scope.engine->throwTypeError(); |
| return nullptr; |
| } |
| Heap::Object *proto = trapResult->isNull() ? nullptr : static_cast<Heap::Object *>(trapResult->heapObject()); |
| if (!target->isExtensible()) { |
| Heap::Object *targetProto = target->getPrototypeOf(); |
| if (proto != targetProto) { |
| scope.engine->throwTypeError(); |
| return nullptr; |
| } |
| } |
| return proto; |
| } |
| |
| bool ProxyObject::virtualSetPrototypeOf(Managed *m, const Object *p) |
| { |
| Scope scope(m); |
| const ProxyObject *o = static_cast<const ProxyObject *>(m); |
| if (!o->d()->handler) { |
| scope.engine->throwTypeError(); |
| return false; |
| } |
| |
| ScopedObject target(scope, o->d()->target); |
| Q_ASSERT(target); |
| ScopedObject handler(scope, o->d()->handler); |
| ScopedString name(scope, scope.engine->newString(QStringLiteral("setPrototypeOf"))); |
| ScopedValue trap(scope, handler->get(name)); |
| if (scope.hasException()) |
| return false; |
| if (trap->isNullOrUndefined()) |
| return target->setPrototypeOf(p); |
| if (!trap->isFunctionObject()) { |
| scope.engine->throwTypeError(); |
| return false; |
| } |
| |
| JSCallData cdata(scope, 2, nullptr, handler); |
| cdata.args[0] = target; |
| cdata.args[1] = p ? p->asReturnedValue() : Encode::null(); |
| |
| ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); |
| bool result = !scope.engine->hasException && trapResult->toBoolean(); |
| if (!result) |
| return false; |
| if (!target->isExtensible()) { |
| Heap::Object *targetProto = target->getPrototypeOf(); |
| if (p->d() != targetProto) { |
| scope.engine->throwTypeError(); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| struct ProxyObjectOwnPropertyKeyIterator : OwnPropertyKeyIterator |
| { |
| PersistentValue ownKeys; |
| uint index = 0; |
| uint len = 0; |
| |
| ProxyObjectOwnPropertyKeyIterator(ArrayObject *keys); |
| ~ProxyObjectOwnPropertyKeyIterator() override = default; |
| PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; |
| |
| }; |
| |
| ProxyObjectOwnPropertyKeyIterator::ProxyObjectOwnPropertyKeyIterator(ArrayObject *keys) |
| { |
| ownKeys = keys; |
| len = keys->getLength(); |
| } |
| |
| PropertyKey ProxyObjectOwnPropertyKeyIterator::next(const Object *m, Property *pd, PropertyAttributes *attrs) |
| { |
| if (index >= len || m == nullptr) |
| return PropertyKey::invalid(); |
| |
| Scope scope(m); |
| ScopedObject keys(scope, ownKeys.asManaged()); |
| PropertyKey key = PropertyKey::fromId(keys->get(PropertyKey::fromArrayIndex(index))); |
| ++index; |
| |
| if (pd || attrs) { |
| ScopedProperty p(scope); |
| PropertyAttributes a = const_cast<Object *>(m)->getOwnProperty(key, pd ? pd : p); |
| if (attrs) |
| *attrs = a; |
| } |
| |
| return key; |
| } |
| |
| static bool removeAllOccurrences(ArrayObject *target, ReturnedValue val) { |
| uint len = target->getLength(); |
| bool found = false; |
| for (uint i = 0; i < len; ++i) { |
| ReturnedValue v = target->get(i); |
| if (v == val) { |
| found = true; |
| target->put(i, Value::undefinedValue()); |
| } |
| } |
| return found; |
| } |
| |
| OwnPropertyKeyIterator *ProxyObject::virtualOwnPropertyKeys(const Object *m, Value *iteratorTarget) |
| { |
| Scope scope(m); |
| const ProxyObject *o = static_cast<const ProxyObject *>(m); |
| if (!o->d()->handler) { |
| scope.engine->throwTypeError(); |
| return nullptr; |
| } |
| |
| ScopedObject target(scope, o->d()->target); |
| Q_ASSERT(target); |
| ScopedObject handler(scope, o->d()->handler); |
| ScopedString name(scope, scope.engine->newString(QStringLiteral("ownKeys"))); |
| ScopedValue trap(scope, handler->get(name)); |
| |
| if (scope.hasException()) |
| return nullptr; |
| if (trap->isUndefined()) |
| return target->ownPropertyKeys(iteratorTarget); |
| if (!trap->isFunctionObject()) { |
| scope.engine->throwTypeError(); |
| return nullptr; |
| } |
| |
| JSCallData cdata(scope, 1, nullptr, handler); |
| cdata.args[0] = target; |
| ScopedObject trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(cdata)); |
| if (scope.engine->hasException) |
| return nullptr; |
| if (!trapResult) { |
| scope.engine->throwTypeError(); |
| return nullptr; |
| } |
| |
| uint len = trapResult->getLength(); |
| ScopedArrayObject trapKeys(scope, scope.engine->newArrayObject()); |
| ScopedStringOrSymbol key(scope); |
| for (uint i = 0; i < len; ++i) { |
| key = trapResult->get(i); |
| if (scope.engine->hasException) |
| return nullptr; |
| if (!key) { |
| scope.engine->throwTypeError(); |
| return nullptr; |
| } |
| Value keyAsValue = Value::fromReturnedValue(key->toPropertyKey().id()); |
| trapKeys->push_back(keyAsValue); |
| } |
| |
| ScopedArrayObject targetConfigurableKeys(scope, scope.engine->newArrayObject()); |
| ScopedArrayObject targetNonConfigurableKeys(scope, scope.engine->newArrayObject()); |
| ObjectIterator it(scope, target, ObjectIterator::EnumerableOnly); |
| ScopedPropertyKey k(scope); |
| while (1) { |
| PropertyAttributes attrs; |
| k = it.next(nullptr, &attrs); |
| if (!k->isValid()) |
| break; |
| Value keyAsValue = Value::fromReturnedValue(k->id()); |
| if (attrs.isConfigurable()) |
| targetConfigurableKeys->push_back(keyAsValue); |
| else |
| targetNonConfigurableKeys->push_back(keyAsValue); |
| } |
| if (target->isExtensible() && targetNonConfigurableKeys->getLength() == 0) |
| return new ProxyObjectOwnPropertyKeyIterator(trapKeys); |
| |
| ScopedArrayObject uncheckedResultKeys(scope, scope.engine->newArrayObject()); |
| uncheckedResultKeys->copyArrayData(trapKeys); |
| |
| len = targetNonConfigurableKeys->getLength(); |
| for (uint i = 0; i < len; ++i) { |
| k = PropertyKey::fromId(targetNonConfigurableKeys->get(i)); |
| if (!removeAllOccurrences(uncheckedResultKeys, k->id())) { |
| scope.engine->throwTypeError(); |
| return nullptr; |
| } |
| } |
| |
| if (target->isExtensible()) |
| return new ProxyObjectOwnPropertyKeyIterator(trapKeys); |
| |
| len = targetConfigurableKeys->getLength(); |
| for (uint i = 0; i < len; ++i) { |
| k = PropertyKey::fromId(targetConfigurableKeys->get(i)); |
| if (!removeAllOccurrences(uncheckedResultKeys, k->id())) { |
| scope.engine->throwTypeError(); |
| return nullptr; |
| } |
| } |
| |
| len = uncheckedResultKeys->getLength(); |
| for (uint i = 0; i < len; ++i) { |
| if (uncheckedResultKeys->get(i) != Encode::undefined()) { |
| scope.engine->throwTypeError(); |
| return nullptr; |
| } |
| } |
| |
| *iteratorTarget = *m; |
| return new ProxyObjectOwnPropertyKeyIterator(trapKeys); |
| } |
| |
| |
| ReturnedValue ProxyFunctionObject::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) |
| { |
| Scope scope(f); |
| const ProxyObject *o = static_cast<const ProxyObject *>(f); |
| if (!o->d()->handler) |
| return scope.engine->throwTypeError(); |
| |
| ScopedFunctionObject target(scope, o->d()->target); |
| Q_ASSERT(target); |
| ScopedObject handler(scope, o->d()->handler); |
| ScopedString name(scope, scope.engine->newString(QStringLiteral("construct"))); |
| ScopedValue trap(scope, handler->get(name)); |
| |
| if (scope.hasException()) |
| return Encode::undefined(); |
| if (trap->isNullOrUndefined()) { |
| Q_ASSERT(target->isConstructor()); |
| return target->callAsConstructor(argv, argc, newTarget); |
| } |
| if (!trap->isFunctionObject()) |
| return scope.engine->throwTypeError(); |
| |
| ScopedFunctionObject trapFunction(scope, trap); |
| Value *arguments = scope.alloc(3); |
| arguments[0] = target; |
| arguments[1] = scope.engine->newArrayObject(argv, argc); |
| arguments[2] = newTarget ? *newTarget : Value::undefinedValue(); |
| ScopedObject result(scope, trapFunction->call(handler, arguments, 3)); |
| |
| if (!result) |
| return scope.engine->throwTypeError(); |
| return result->asReturnedValue(); |
| } |
| |
| ReturnedValue ProxyFunctionObject::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
| { |
| Scope scope(f); |
| |
| const ProxyObject *o = static_cast<const ProxyObject *>(f); |
| if (!o->d()->handler) |
| return scope.engine->throwTypeError(); |
| |
| ScopedFunctionObject target(scope, o->d()->target); |
| Q_ASSERT(target); |
| ScopedObject handler(scope, o->d()->handler); |
| ScopedString name(scope, scope.engine->newString(QStringLiteral("apply"))); |
| ScopedValue trap(scope, handler->get(name)); |
| |
| if (scope.hasException()) |
| return Encode::undefined(); |
| if (trap->isNullOrUndefined()) |
| return checkedResult(scope.engine, target->call(thisObject, argv, argc)); |
| if (!trap->isFunctionObject()) |
| return scope.engine->throwTypeError(); |
| |
| ScopedFunctionObject trapFunction(scope, trap); |
| Value *arguments = scope.alloc(3); |
| arguments[0] = target; |
| arguments[1] = thisObject ? *thisObject : Value::undefinedValue(); |
| arguments[2] = scope.engine->newArrayObject(argv, argc); |
| return trapFunction->call(handler, arguments, 3); |
| } |
| |
| DEFINE_OBJECT_VTABLE(Proxy); |
| |
| void Heap::Proxy::init(QV4::ExecutionContext *ctx) |
| { |
| Heap::FunctionObject::init(ctx, QStringLiteral("Proxy")); |
| |
| Scope scope(ctx); |
| Scoped<QV4::Proxy> ctor(scope, this); |
| ctor->defineDefaultProperty(QStringLiteral("revocable"), QV4::Proxy::method_revocable, 2); |
| ctor->defineReadonlyConfigurableProperty(scope.engine->id_length(), Value::fromInt32(2)); |
| } |
| |
| ReturnedValue Proxy::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *) |
| { |
| Scope scope(f); |
| if (argc < 2 || !argv[0].isObject() || !argv[1].isObject()) |
| return scope.engine->throwTypeError(); |
| |
| const Object *target = static_cast<const Object *>(argv); |
| const Object *handler = static_cast<const Object *>(argv + 1); |
| if (const ProxyObject *ptarget = target->as<ProxyObject>()) |
| if (!ptarget->d()->handler) |
| return scope.engine->throwTypeError(); |
| if (const ProxyObject *phandler = handler->as<ProxyObject>()) |
| if (!phandler->d()->handler) |
| return scope.engine->throwTypeError(); |
| |
| const FunctionObject *targetFunction = target->as<FunctionObject>(); |
| if (targetFunction) |
| return scope.engine->memoryManager->allocate<ProxyFunctionObject>(targetFunction, handler)->asReturnedValue(); |
| return scope.engine->memoryManager->allocate<ProxyObject>(target, handler)->asReturnedValue(); |
| } |
| |
| ReturnedValue Proxy::virtualCall(const FunctionObject *f, const Value *, const Value *, int) |
| { |
| return f->engine()->throwTypeError(); |
| } |
| |
| ReturnedValue Proxy::method_revocable(const FunctionObject *f, const Value *, const Value *argv, int argc) |
| { |
| Scope scope(f); |
| ScopedObject proxy(scope, Proxy::virtualCallAsConstructor(f, argv, argc, f)); |
| if (scope.hasException()) |
| return Encode::undefined(); |
| Q_ASSERT(proxy); |
| |
| ScopedString revoke(scope, scope.engine->newString(QStringLiteral("revoke"))); |
| ScopedFunctionObject revoker(scope, scope.engine->memoryManager->allocate<FunctionObject>(scope.engine->rootContext(), nullptr, method_revoke)); |
| revoker->defineReadonlyConfigurableProperty(scope.engine->id_length(), Value::fromInt32(0)); |
| revoker->defineDefaultProperty(scope.engine->symbol_revokableProxy(), proxy); |
| |
| ScopedObject o(scope, scope.engine->newObject()); |
| ScopedString p(scope, scope.engine->newString(QStringLiteral("proxy"))); |
| o->defineDefaultProperty(p, proxy); |
| o->defineDefaultProperty(revoke, revoker); |
| return o->asReturnedValue(); |
| } |
| |
| ReturnedValue Proxy::method_revoke(const FunctionObject *f, const Value *, const Value *, int) |
| { |
| Scope scope(f); |
| ScopedObject o(scope, f->get(scope.engine->symbol_revokableProxy())); |
| Q_ASSERT(o); |
| ProxyObject *proxy = o->cast<ProxyObject>(); |
| |
| proxy->d()->target.set(scope.engine, nullptr); |
| proxy->d()->handler.set(scope.engine, nullptr); |
| return Encode::undefined(); |
| } |