/**************************************************************************** ** ** 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(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(trap.ptr)->call(cdata)); 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(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(trap.ptr)->call(cdata)); if (!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(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(trap.ptr)->call(cdata)); if (!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(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(trap.ptr)->call(cdata)); 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(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(trap.ptr)->call(cdata)); 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(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(trap.ptr)->call(cdata)); bool result = 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(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(trap.ptr)->call(cdata)); 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(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(trap.ptr)->call(cdata)); 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(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(trap.ptr)->call(cdata)); if (!trapResult->isNull() && !trapResult->isObject()) { scope.engine->throwTypeError(); return nullptr; } Heap::Object *proto = trapResult->isNull() ? nullptr : static_cast(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(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(trap.ptr)->call(cdata)); bool result = 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) 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(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(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(trap.ptr)->call(cdata)); 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(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(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 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 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(argv); const Object *handler = static_cast(argv + 1); if (const ProxyObject *ptarget = target->as()) if (!ptarget->d()->handler) return scope.engine->throwTypeError(); if (const ProxyObject *phandler = handler->as()) if (!phandler->d()->handler) return scope.engine->throwTypeError(); const FunctionObject *targetFunction = target->as(); if (targetFunction) return scope.engine->memoryManager->allocate(targetFunction, handler)->asReturnedValue(); return scope.engine->memoryManager->allocate(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(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(); proxy->d()->target.set(scope.engine, nullptr); proxy->d()->handler.set(scope.engine, nullptr); return Encode::undefined(); }