summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/runtime/ProxyObject.cpp
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/JavaScriptCore/runtime/ProxyObject.cpp
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/JavaScriptCore/runtime/ProxyObject.cpp')
-rw-r--r--Source/JavaScriptCore/runtime/ProxyObject.cpp1164
1 files changed, 1164 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/runtime/ProxyObject.cpp b/Source/JavaScriptCore/runtime/ProxyObject.cpp
new file mode 100644
index 000000000..a9b64ea76
--- /dev/null
+++ b/Source/JavaScriptCore/runtime/ProxyObject.cpp
@@ -0,0 +1,1164 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "ProxyObject.h"
+
+#include "ArrayConstructor.h"
+#include "Error.h"
+#include "IdentifierInlines.h"
+#include "JSCInlines.h"
+#include "JSObjectInlines.h"
+#include "ObjectConstructor.h"
+#include "SlotVisitorInlines.h"
+#include "StructureInlines.h"
+#include "VMInlines.h"
+
+// Note that this file is compile with -fno-optimize-sibling-calls because we rely on the machine stack
+// growing larger for throwing OOM errors for when we have an effectively cyclic prototype chain.
+
+namespace JSC {
+
+STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(ProxyObject);
+
+const ClassInfo ProxyObject::s_info = { "ProxyObject", &Base::s_info, 0, CREATE_METHOD_TABLE(ProxyObject) };
+
+ProxyObject::ProxyObject(VM& vm, Structure* structure)
+ : Base(vm, structure)
+{
+}
+
+String ProxyObject::toStringName(const JSObject* object, ExecState* exec)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ const ProxyObject* proxy = jsCast<const ProxyObject*>(object);
+ while (proxy) {
+ const JSObject* target = proxy->target();
+ if (isArray(exec, target))
+ return target->classInfo(vm)->methodTable.toStringName(target, exec);
+ if (UNLIKELY(scope.exception()))
+ break;
+
+ proxy = jsDynamicCast<const ProxyObject*>(vm, target);
+ }
+ return ASCIILiteral("Object");
+}
+
+Structure* ProxyObject::structureForTarget(JSGlobalObject* globalObject, JSValue target)
+{
+ if (!target.isObject())
+ return globalObject->proxyObjectStructure();
+
+ JSObject* targetAsObject = jsCast<JSObject*>(target);
+ CallData ignoredCallData;
+ VM& vm = globalObject->vm();
+ bool isCallable = targetAsObject->methodTable(vm)->getCallData(targetAsObject, ignoredCallData) != CallType::None;
+ return isCallable ? globalObject->callableProxyObjectStructure() : globalObject->proxyObjectStructure();
+}
+
+void ProxyObject::finishCreation(VM& vm, ExecState* exec, JSValue target, JSValue handler)
+{
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ Base::finishCreation(vm);
+ if (!target.isObject()) {
+ throwTypeError(exec, scope, ASCIILiteral("A Proxy's 'target' should be an Object"));
+ return;
+ }
+ if (ProxyObject* targetAsProxy = jsDynamicCast<ProxyObject*>(vm, target)) {
+ if (targetAsProxy->handler().isNull()) {
+ throwTypeError(exec, scope, ASCIILiteral("If a Proxy's handler is another Proxy object, the other Proxy should not have been revoked"));
+ return;
+ }
+ }
+ if (!handler.isObject()) {
+ throwTypeError(exec, scope, ASCIILiteral("A Proxy's 'handler' should be an Object"));
+ return;
+ }
+
+ JSObject* targetAsObject = jsCast<JSObject*>(target);
+
+ CallData ignoredCallData;
+ m_isCallable = targetAsObject->methodTable(vm)->getCallData(targetAsObject, ignoredCallData) != CallType::None;
+ if (m_isCallable) {
+ TypeInfo info = structure(vm)->typeInfo();
+ RELEASE_ASSERT(info.implementsHasInstance() && info.implementsDefaultHasInstance());
+ }
+
+ ConstructData ignoredConstructData;
+ m_isConstructible = jsCast<JSObject*>(target)->methodTable(vm)->getConstructData(jsCast<JSObject*>(target), ignoredConstructData) != ConstructType::None;
+
+ m_target.set(vm, this, targetAsObject);
+ m_handler.set(vm, this, handler);
+}
+
+static const char* s_proxyAlreadyRevokedErrorMessage = "Proxy has already been revoked. No more operations are allowed to be performed on it";
+
+static JSValue performProxyGet(ExecState* exec, ProxyObject* proxyObject, JSValue receiver, PropertyName propertyName)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
+ throwStackOverflowError(exec, scope);
+ return { };
+ }
+
+ JSObject* target = proxyObject->target();
+
+ auto performDefaultGet = [&] {
+ return target->get(exec, propertyName);
+ };
+
+ if (vm.propertyNames->isPrivateName(Identifier::fromUid(&vm, propertyName.uid()))) {
+ scope.release();
+ return performDefaultGet();
+ }
+
+ JSValue handlerValue = proxyObject->handler();
+ if (handlerValue.isNull())
+ return throwTypeError(exec, scope, ASCIILiteral(s_proxyAlreadyRevokedErrorMessage));
+
+ JSObject* handler = jsCast<JSObject*>(handlerValue);
+ CallData callData;
+ CallType callType;
+ JSValue getHandler = handler->getMethod(exec, callData, callType, vm.propertyNames->get, ASCIILiteral("'get' property of a Proxy's handler object should be callable"));
+ RETURN_IF_EXCEPTION(scope, { });
+
+ if (getHandler.isUndefined()) {
+ scope.release();
+ return performDefaultGet();
+ }
+
+ MarkedArgumentBuffer arguments;
+ arguments.append(target);
+ arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(&vm, propertyName.uid())));
+ arguments.append(receiver);
+ JSValue trapResult = call(exec, getHandler, callType, callData, handler, arguments);
+ RETURN_IF_EXCEPTION(scope, { });
+
+ PropertyDescriptor descriptor;
+ if (target->getOwnPropertyDescriptor(exec, propertyName, descriptor)) {
+ if (descriptor.isDataDescriptor() && !descriptor.configurable() && !descriptor.writable()) {
+ if (!sameValue(exec, descriptor.value(), trapResult))
+ return throwTypeError(exec, scope, ASCIILiteral("Proxy handler's 'get' result of a non-configurable and non-writable property should be the same value as the target's property"));
+ } else if (descriptor.isAccessorDescriptor() && !descriptor.configurable() && descriptor.getter().isUndefined()) {
+ if (!trapResult.isUndefined())
+ return throwTypeError(exec, scope, ASCIILiteral("Proxy handler's 'get' result of a non-configurable accessor property without a getter should be undefined"));
+ }
+ }
+
+ RETURN_IF_EXCEPTION(scope, { });
+
+ return trapResult;
+}
+
+bool ProxyObject::performGet(ExecState* exec, PropertyName propertyName, PropertySlot& slot)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ JSValue result = performProxyGet(exec, this, slot.thisValue(), propertyName);
+ RETURN_IF_EXCEPTION(scope, false);
+ unsigned ignoredAttributes = 0;
+ slot.setValue(this, ignoredAttributes, result);
+ return true;
+}
+
+bool ProxyObject::performInternalMethodGetOwnProperty(ExecState* exec, PropertyName propertyName, PropertySlot& slot)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
+ throwStackOverflowError(exec, scope);
+ return false;
+ }
+ JSObject* target = this->target();
+
+ auto performDefaultGetOwnProperty = [&] {
+ return target->methodTable(vm)->getOwnPropertySlot(target, exec, propertyName, slot);
+ };
+
+ if (vm.propertyNames->isPrivateName(Identifier::fromUid(&vm, propertyName.uid()))) {
+ scope.release();
+ return performDefaultGetOwnProperty();
+ }
+
+ JSValue handlerValue = this->handler();
+ if (handlerValue.isNull()) {
+ throwVMTypeError(exec, scope, ASCIILiteral(s_proxyAlreadyRevokedErrorMessage));
+ return false;
+ }
+
+ JSObject* handler = jsCast<JSObject*>(handlerValue);
+ CallData callData;
+ CallType callType;
+ JSValue getOwnPropertyDescriptorMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "getOwnPropertyDescriptor"), ASCIILiteral("'getOwnPropertyDescriptor' property of a Proxy's handler should be callable"));
+ RETURN_IF_EXCEPTION(scope, false);
+ if (getOwnPropertyDescriptorMethod.isUndefined()) {
+ scope.release();
+ return performDefaultGetOwnProperty();
+ }
+
+ MarkedArgumentBuffer arguments;
+ arguments.append(target);
+ arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(&vm, propertyName.uid())));
+ JSValue trapResult = call(exec, getOwnPropertyDescriptorMethod, callType, callData, handler, arguments);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ if (!trapResult.isUndefined() && !trapResult.isObject()) {
+ throwVMTypeError(exec, scope, ASCIILiteral("result of 'getOwnPropertyDescriptor' call should either be an Object or undefined"));
+ return false;
+ }
+
+ PropertyDescriptor targetPropertyDescriptor;
+ bool isTargetPropertyDescriptorDefined = target->getOwnPropertyDescriptor(exec, propertyName, targetPropertyDescriptor);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ if (trapResult.isUndefined()) {
+ if (!isTargetPropertyDescriptorDefined)
+ return false;
+ if (!targetPropertyDescriptor.configurable()) {
+ throwVMTypeError(exec, scope, ASCIILiteral("When the result of 'getOwnPropertyDescriptor' is undefined the target must be configurable"));
+ return false;
+ }
+ // FIXME: this doesn't work if 'target' is another Proxy. We don't have isExtensible implemented in a way that fits w/ Proxys.
+ // https://bugs.webkit.org/show_bug.cgi?id=154375
+ bool isExtensible = target->isExtensible(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+ if (!isExtensible) {
+ // FIXME: Come up with a test for this error. I'm not sure how to because
+ // Object.seal(o) will make all fields [[Configurable]] false.
+ // https://bugs.webkit.org/show_bug.cgi?id=154376
+ throwVMTypeError(exec, scope, ASCIILiteral("When 'getOwnPropertyDescriptor' returns undefined, the 'target' of a Proxy should be extensible"));
+ return false;
+ }
+
+ return false;
+ }
+
+ bool isExtensible = target->isExtensible(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+ PropertyDescriptor trapResultAsDescriptor;
+ toPropertyDescriptor(exec, trapResult, trapResultAsDescriptor);
+ RETURN_IF_EXCEPTION(scope, false);
+ bool throwException = false;
+ bool valid = validateAndApplyPropertyDescriptor(exec, nullptr, propertyName, isExtensible,
+ trapResultAsDescriptor, isTargetPropertyDescriptorDefined, targetPropertyDescriptor, throwException);
+ RETURN_IF_EXCEPTION(scope, false);
+ if (!valid) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Result from 'getOwnPropertyDescriptor' fails the IsCompatiblePropertyDescriptor test"));
+ return false;
+ }
+
+ if (!trapResultAsDescriptor.configurable()) {
+ if (!isTargetPropertyDescriptorDefined || targetPropertyDescriptor.configurable()) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Result from 'getOwnPropertyDescriptor' can't be non-configurable when the 'target' doesn't have it as an own property or if it is a configurable own property on 'target'"));
+ return false;
+ }
+ }
+
+ if (trapResultAsDescriptor.isAccessorDescriptor()) {
+ GetterSetter* getterSetter = trapResultAsDescriptor.slowGetterSetter(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+ slot.setGetterSlot(this, trapResultAsDescriptor.attributes(), getterSetter);
+ } else if (trapResultAsDescriptor.isDataDescriptor() && !trapResultAsDescriptor.value().isEmpty())
+ slot.setValue(this, trapResultAsDescriptor.attributes(), trapResultAsDescriptor.value());
+ else
+ slot.setValue(this, trapResultAsDescriptor.attributes(), jsUndefined()); // We use undefined because it's the default value in object properties.
+
+ return true;
+}
+
+bool ProxyObject::performHasProperty(ExecState* exec, PropertyName propertyName, PropertySlot& slot)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
+ throwStackOverflowError(exec, scope);
+ return false;
+ }
+ JSObject* target = this->target();
+ slot.setValue(this, None, jsUndefined()); // Nobody should rely on our value, but be safe and protect against any bad actors reading our value.
+
+ auto performDefaultHasProperty = [&] {
+ return target->methodTable(vm)->getOwnPropertySlot(target, exec, propertyName, slot);
+ };
+
+ if (vm.propertyNames->isPrivateName(Identifier::fromUid(&vm, propertyName.uid()))) {
+ scope.release();
+ return performDefaultHasProperty();
+ }
+
+ JSValue handlerValue = this->handler();
+ if (handlerValue.isNull()) {
+ throwVMTypeError(exec, scope, ASCIILiteral(s_proxyAlreadyRevokedErrorMessage));
+ return false;
+ }
+
+ JSObject* handler = jsCast<JSObject*>(handlerValue);
+ CallData callData;
+ CallType callType;
+ JSValue hasMethod = handler->getMethod(exec, callData, callType, vm.propertyNames->has, ASCIILiteral("'has' property of a Proxy's handler should be callable"));
+ RETURN_IF_EXCEPTION(scope, false);
+ if (hasMethod.isUndefined()) {
+ scope.release();
+ return performDefaultHasProperty();
+ }
+
+ MarkedArgumentBuffer arguments;
+ arguments.append(target);
+ arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(&vm, propertyName.uid())));
+ JSValue trapResult = call(exec, hasMethod, callType, callData, handler, arguments);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ bool trapResultAsBool = trapResult.toBoolean(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ if (!trapResultAsBool) {
+ PropertyDescriptor descriptor;
+ bool isPropertyDescriptorDefined = target->getOwnPropertyDescriptor(exec, propertyName, descriptor);
+ RETURN_IF_EXCEPTION(scope, false);
+ if (isPropertyDescriptorDefined) {
+ if (!descriptor.configurable()) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy 'has' must return 'true' for non-configurable properties"));
+ return false;
+ }
+ bool isExtensible = target->isExtensible(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+ if (!isExtensible) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy 'has' must return 'true' for a non-extensible 'target' object with a configurable property"));
+ return false;
+ }
+ }
+ }
+
+ return trapResultAsBool;
+}
+
+bool ProxyObject::getOwnPropertySlotCommon(ExecState* exec, PropertyName propertyName, PropertySlot& slot)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
+ throwStackOverflowError(exec, scope);
+ return false;
+ }
+ slot.disableCaching();
+ slot.setIsTaintedByOpaqueObject();
+ switch (slot.internalMethodType()) {
+ case PropertySlot::InternalMethodType::Get:
+ scope.release();
+ return performGet(exec, propertyName, slot);
+ case PropertySlot::InternalMethodType::GetOwnProperty:
+ scope.release();
+ return performInternalMethodGetOwnProperty(exec, propertyName, slot);
+ case PropertySlot::InternalMethodType::HasProperty:
+ scope.release();
+ return performHasProperty(exec, propertyName, slot);
+ default:
+ return false;
+ }
+
+ RELEASE_ASSERT_NOT_REACHED();
+ return false;
+}
+
+bool ProxyObject::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
+{
+ ProxyObject* thisObject = jsCast<ProxyObject*>(object);
+ return thisObject->getOwnPropertySlotCommon(exec, propertyName, slot);
+}
+
+bool ProxyObject::getOwnPropertySlotByIndex(JSObject* object, ExecState* exec, unsigned propertyName, PropertySlot& slot)
+{
+ ProxyObject* thisObject = jsCast<ProxyObject*>(object);
+ Identifier ident = Identifier::from(exec, propertyName);
+ return thisObject->getOwnPropertySlotCommon(exec, ident.impl(), slot);
+}
+
+template <typename PerformDefaultPutFunction>
+bool ProxyObject::performPut(ExecState* exec, JSValue putValue, JSValue thisValue, PropertyName propertyName, PerformDefaultPutFunction performDefaultPut)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
+ throwStackOverflowError(exec, scope);
+ return false;
+ }
+
+ if (vm.propertyNames->isPrivateName(Identifier::fromUid(&vm, propertyName.uid()))) {
+ scope.release();
+ return performDefaultPut();
+ }
+
+ JSValue handlerValue = this->handler();
+ if (handlerValue.isNull()) {
+ throwVMTypeError(exec, scope, ASCIILiteral(s_proxyAlreadyRevokedErrorMessage));
+ return false;
+ }
+
+ JSObject* handler = jsCast<JSObject*>(handlerValue);
+ CallData callData;
+ CallType callType;
+ JSValue setMethod = handler->getMethod(exec, callData, callType, vm.propertyNames->set, ASCIILiteral("'set' property of a Proxy's handler should be callable"));
+ RETURN_IF_EXCEPTION(scope, false);
+ JSObject* target = this->target();
+ if (setMethod.isUndefined()) {
+ scope.release();
+ return performDefaultPut();
+ }
+
+ MarkedArgumentBuffer arguments;
+ arguments.append(target);
+ arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(&vm, propertyName.uid())));
+ arguments.append(putValue);
+ arguments.append(thisValue);
+ JSValue trapResult = call(exec, setMethod, callType, callData, handler, arguments);
+ RETURN_IF_EXCEPTION(scope, false);
+ bool trapResultAsBool = trapResult.toBoolean(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+ if (!trapResultAsBool)
+ return false;
+
+ PropertyDescriptor descriptor;
+ if (target->getOwnPropertyDescriptor(exec, propertyName, descriptor)) {
+ if (descriptor.isDataDescriptor() && !descriptor.configurable() && !descriptor.writable()) {
+ if (!sameValue(exec, descriptor.value(), putValue)) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy handler's 'set' on a non-configurable and non-writable property on 'target' should either return false or be the same value already on the 'target'"));
+ return false;
+ }
+ } else if (descriptor.isAccessorDescriptor() && !descriptor.configurable() && descriptor.setter().isUndefined()) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy handler's 'set' method on a non-configurable accessor property without a setter should return false"));
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ProxyObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
+{
+ VM& vm = exec->vm();
+ slot.disableCaching();
+
+ ProxyObject* thisObject = jsCast<ProxyObject*>(cell);
+ auto performDefaultPut = [&] () {
+ JSObject* target = jsCast<JSObject*>(thisObject->target());
+ return target->methodTable(vm)->put(target, exec, propertyName, value, slot);
+ };
+ return thisObject->performPut(exec, value, slot.thisValue(), propertyName, performDefaultPut);
+}
+
+bool ProxyObject::putByIndexCommon(ExecState* exec, JSValue thisValue, unsigned propertyName, JSValue putValue, bool shouldThrow)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ Identifier ident = Identifier::from(exec, propertyName);
+ RETURN_IF_EXCEPTION(scope, false);
+ auto performDefaultPut = [&] () {
+ JSObject* target = this->target();
+ bool isStrictMode = shouldThrow;
+ PutPropertySlot slot(thisValue, isStrictMode); // We must preserve the "this" target of the putByIndex.
+ return target->methodTable(vm)->put(target, exec, ident.impl(), putValue, slot);
+ };
+ scope.release();
+ return performPut(exec, putValue, thisValue, ident.impl(), performDefaultPut);
+}
+
+bool ProxyObject::putByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow)
+{
+ ProxyObject* thisObject = jsCast<ProxyObject*>(cell);
+ return thisObject->putByIndexCommon(exec, thisObject, propertyName, value, shouldThrow);
+}
+
+static EncodedJSValue JSC_HOST_CALL performProxyCall(ExecState* exec)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
+ throwStackOverflowError(exec, scope);
+ return encodedJSValue();
+ }
+ ProxyObject* proxy = jsCast<ProxyObject*>(exec->jsCallee());
+ JSValue handlerValue = proxy->handler();
+ if (handlerValue.isNull())
+ return throwVMTypeError(exec, scope, ASCIILiteral(s_proxyAlreadyRevokedErrorMessage));
+
+ JSObject* handler = jsCast<JSObject*>(handlerValue);
+ CallData callData;
+ CallType callType;
+ JSValue applyMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "apply"), ASCIILiteral("'apply' property of a Proxy's handler should be callable"));
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+ JSObject* target = proxy->target();
+ if (applyMethod.isUndefined()) {
+ CallData callData;
+ CallType callType = target->methodTable(vm)->getCallData(target, callData);
+ RELEASE_ASSERT(callType != CallType::None);
+ scope.release();
+ return JSValue::encode(call(exec, target, callType, callData, exec->thisValue(), ArgList(exec)));
+ }
+
+ JSArray* argArray = constructArray(exec, static_cast<ArrayAllocationProfile*>(nullptr), ArgList(exec));
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+ MarkedArgumentBuffer arguments;
+ arguments.append(target);
+ arguments.append(exec->thisValue());
+ arguments.append(argArray);
+ scope.release();
+ return JSValue::encode(call(exec, applyMethod, callType, callData, handler, arguments));
+}
+
+CallType ProxyObject::getCallData(JSCell* cell, CallData& callData)
+{
+ ProxyObject* proxy = jsCast<ProxyObject*>(cell);
+ if (!proxy->m_isCallable) {
+ callData.js.functionExecutable = nullptr;
+ callData.js.scope = nullptr;
+ return CallType::None;
+ }
+
+ callData.native.function = performProxyCall;
+ return CallType::Host;
+}
+
+static EncodedJSValue JSC_HOST_CALL performProxyConstruct(ExecState* exec)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
+ throwStackOverflowError(exec, scope);
+ return encodedJSValue();
+ }
+ ProxyObject* proxy = jsCast<ProxyObject*>(exec->jsCallee());
+ JSValue handlerValue = proxy->handler();
+ if (handlerValue.isNull())
+ return throwVMTypeError(exec, scope, ASCIILiteral(s_proxyAlreadyRevokedErrorMessage));
+
+ JSObject* handler = jsCast<JSObject*>(handlerValue);
+ CallData callData;
+ CallType callType;
+ JSValue constructMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "construct"), ASCIILiteral("'construct' property of a Proxy's handler should be constructible"));
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+ JSObject* target = proxy->target();
+ if (constructMethod.isUndefined()) {
+ ConstructData constructData;
+ ConstructType constructType = target->methodTable(vm)->getConstructData(target, constructData);
+ RELEASE_ASSERT(constructType != ConstructType::None);
+ scope.release();
+ return JSValue::encode(construct(exec, target, constructType, constructData, ArgList(exec), exec->newTarget()));
+ }
+
+ JSArray* argArray = constructArray(exec, static_cast<ArrayAllocationProfile*>(nullptr), ArgList(exec));
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+ MarkedArgumentBuffer arguments;
+ arguments.append(target);
+ arguments.append(argArray);
+ arguments.append(exec->newTarget());
+ JSValue result = call(exec, constructMethod, callType, callData, handler, arguments);
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+ if (!result.isObject())
+ return throwVMTypeError(exec, scope, ASCIILiteral("Result from Proxy handler's 'construct' method should be an object"));
+ return JSValue::encode(result);
+}
+
+ConstructType ProxyObject::getConstructData(JSCell* cell, ConstructData& constructData)
+{
+ ProxyObject* proxy = jsCast<ProxyObject*>(cell);
+ if (!proxy->m_isConstructible) {
+ constructData.js.functionExecutable = nullptr;
+ constructData.js.scope = nullptr;
+ return ConstructType::None;
+ }
+
+ constructData.native.function = performProxyConstruct;
+ return ConstructType::Host;
+}
+
+template <typename DefaultDeleteFunction>
+bool ProxyObject::performDelete(ExecState* exec, PropertyName propertyName, DefaultDeleteFunction performDefaultDelete)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
+ throwStackOverflowError(exec, scope);
+ return false;
+ }
+
+ if (vm.propertyNames->isPrivateName(Identifier::fromUid(&vm, propertyName.uid()))) {
+ scope.release();
+ return performDefaultDelete();
+ }
+
+ JSValue handlerValue = this->handler();
+ if (handlerValue.isNull()) {
+ throwVMTypeError(exec, scope, ASCIILiteral(s_proxyAlreadyRevokedErrorMessage));
+ return false;
+ }
+
+ JSObject* handler = jsCast<JSObject*>(handlerValue);
+ CallData callData;
+ CallType callType;
+ JSValue deletePropertyMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "deleteProperty"), ASCIILiteral("'deleteProperty' property of a Proxy's handler should be callable"));
+ RETURN_IF_EXCEPTION(scope, false);
+ JSObject* target = this->target();
+ if (deletePropertyMethod.isUndefined()) {
+ scope.release();
+ return performDefaultDelete();
+ }
+
+ MarkedArgumentBuffer arguments;
+ arguments.append(target);
+ arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(&vm, propertyName.uid())));
+ JSValue trapResult = call(exec, deletePropertyMethod, callType, callData, handler, arguments);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ bool trapResultAsBool = trapResult.toBoolean(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ if (!trapResultAsBool)
+ return false;
+
+ PropertyDescriptor descriptor;
+ if (target->getOwnPropertyDescriptor(exec, propertyName, descriptor)) {
+ if (!descriptor.configurable()) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy handler's 'deleteProperty' method should return false when the target's property is not configurable"));
+ return false;
+ }
+ }
+
+ RETURN_IF_EXCEPTION(scope, false);
+
+ return true;
+}
+
+bool ProxyObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
+{
+ ProxyObject* thisObject = jsCast<ProxyObject*>(cell);
+ auto performDefaultDelete = [&] () -> bool {
+ JSObject* target = thisObject->target();
+ return target->methodTable(exec->vm())->deleteProperty(target, exec, propertyName);
+ };
+ return thisObject->performDelete(exec, propertyName, performDefaultDelete);
+}
+
+bool ProxyObject::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned propertyName)
+{
+ ProxyObject* thisObject = jsCast<ProxyObject*>(cell);
+ Identifier ident = Identifier::from(exec, propertyName);
+ auto performDefaultDelete = [&] () -> bool {
+ JSObject* target = thisObject->target();
+ return target->methodTable(exec->vm())->deletePropertyByIndex(target, exec, propertyName);
+ };
+ return thisObject->performDelete(exec, ident.impl(), performDefaultDelete);
+}
+
+bool ProxyObject::performPreventExtensions(ExecState* exec)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
+ throwStackOverflowError(exec, scope);
+ return false;
+ }
+
+ JSValue handlerValue = this->handler();
+ if (handlerValue.isNull()) {
+ throwVMTypeError(exec, scope, ASCIILiteral(s_proxyAlreadyRevokedErrorMessage));
+ return false;
+ }
+
+ JSObject* handler = jsCast<JSObject*>(handlerValue);
+ CallData callData;
+ CallType callType;
+ JSValue preventExtensionsMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "preventExtensions"), ASCIILiteral("'preventExtensions' property of a Proxy's handler should be callable"));
+ RETURN_IF_EXCEPTION(scope, false);
+ JSObject* target = this->target();
+ if (preventExtensionsMethod.isUndefined()) {
+ scope.release();
+ return target->methodTable(vm)->preventExtensions(target, exec);
+ }
+
+ MarkedArgumentBuffer arguments;
+ arguments.append(target);
+ JSValue trapResult = call(exec, preventExtensionsMethod, callType, callData, handler, arguments);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ bool trapResultAsBool = trapResult.toBoolean(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ if (trapResultAsBool) {
+ bool targetIsExtensible = target->isExtensible(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+ if (targetIsExtensible) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy's 'preventExtensions' trap returned true even though its target is extensible. It should have returned false"));
+ return false;
+ }
+ }
+
+ return trapResultAsBool;
+}
+
+bool ProxyObject::preventExtensions(JSObject* object, ExecState* exec)
+{
+ return jsCast<ProxyObject*>(object)->performPreventExtensions(exec);
+}
+
+bool ProxyObject::performIsExtensible(ExecState* exec)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
+ throwStackOverflowError(exec, scope);
+ return false;
+ }
+
+ JSValue handlerValue = this->handler();
+ if (handlerValue.isNull()) {
+ throwVMTypeError(exec, scope, ASCIILiteral(s_proxyAlreadyRevokedErrorMessage));
+ return false;
+ }
+
+ JSObject* handler = jsCast<JSObject*>(handlerValue);
+ CallData callData;
+ CallType callType;
+ JSValue isExtensibleMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "isExtensible"), ASCIILiteral("'isExtensible' property of a Proxy's handler should be callable"));
+ RETURN_IF_EXCEPTION(scope, false);
+
+ JSObject* target = this->target();
+ if (isExtensibleMethod.isUndefined()) {
+ scope.release();
+ return target->isExtensible(exec);
+ }
+
+ MarkedArgumentBuffer arguments;
+ arguments.append(target);
+ JSValue trapResult = call(exec, isExtensibleMethod, callType, callData, handler, arguments);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ bool trapResultAsBool = trapResult.toBoolean(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ bool isTargetExtensible = target->isExtensible(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ if (trapResultAsBool != isTargetExtensible) {
+ if (isTargetExtensible) {
+ ASSERT(!trapResultAsBool);
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy object's 'isExtensible' trap returned false when the target is extensible. It should have returned true"));
+ } else {
+ ASSERT(!isTargetExtensible);
+ ASSERT(trapResultAsBool);
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy object's 'isExtensible' trap returned true when the target is non-extensible. It should have returned false"));
+ }
+ }
+
+ return trapResultAsBool;
+}
+
+bool ProxyObject::isExtensible(JSObject* object, ExecState* exec)
+{
+ return jsCast<ProxyObject*>(object)->performIsExtensible(exec);
+}
+
+bool ProxyObject::performDefineOwnProperty(ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
+ throwStackOverflowError(exec, scope);
+ return false;
+ }
+
+ JSObject* target = this->target();
+ auto performDefaultDefineOwnProperty = [&] {
+ scope.release();
+ return target->methodTable(vm)->defineOwnProperty(target, exec, propertyName, descriptor, shouldThrow);
+ };
+
+ if (vm.propertyNames->isPrivateName(Identifier::fromUid(&vm, propertyName.uid())))
+ return performDefaultDefineOwnProperty();
+
+ JSValue handlerValue = this->handler();
+ if (handlerValue.isNull()) {
+ throwVMTypeError(exec, scope, ASCIILiteral(s_proxyAlreadyRevokedErrorMessage));
+ return false;
+ }
+
+ JSObject* handler = jsCast<JSObject*>(handlerValue);
+ CallData callData;
+ CallType callType;
+ JSValue definePropertyMethod = handler->getMethod(exec, callData, callType, vm.propertyNames->defineProperty, ASCIILiteral("'defineProperty' property of a Proxy's handler should be callable"));
+ RETURN_IF_EXCEPTION(scope, false);
+
+ if (definePropertyMethod.isUndefined())
+ return performDefaultDefineOwnProperty();
+
+ JSObject* descriptorObject = constructObjectFromPropertyDescriptor(exec, descriptor);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ MarkedArgumentBuffer arguments;
+ arguments.append(target);
+ arguments.append(identifierToSafePublicJSValue(vm, Identifier::fromUid(&vm, propertyName.uid())));
+ arguments.append(descriptorObject);
+ JSValue trapResult = call(exec, definePropertyMethod, callType, callData, handler, arguments);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ bool trapResultAsBool = trapResult.toBoolean(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ if (!trapResultAsBool)
+ return false;
+
+ PropertyDescriptor targetDescriptor;
+ bool isTargetDescriptorDefined = target->getOwnPropertyDescriptor(exec, propertyName, targetDescriptor);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ bool targetIsExtensible = target->isExtensible(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+ bool settingConfigurableToFalse = descriptor.configurablePresent() && !descriptor.configurable();
+
+ if (!isTargetDescriptorDefined) {
+ if (!targetIsExtensible) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy's 'defineProperty' trap returned true even though getOwnPropertyDescriptor of the Proxy's target returned undefined and the target is non-extensible"));
+ return false;
+ }
+ if (settingConfigurableToFalse) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy's 'defineProperty' trap returned true for a non-configurable field even though getOwnPropertyDescriptor of the Proxy's target returned undefined"));
+ return false;
+ }
+
+ return true;
+ }
+
+ ASSERT(isTargetDescriptorDefined);
+ bool isCurrentDefined = isTargetDescriptorDefined;
+ const PropertyDescriptor& current = targetDescriptor;
+ bool throwException = false;
+ bool isCompatibleDescriptor = validateAndApplyPropertyDescriptor(exec, nullptr, propertyName, targetIsExtensible, descriptor, isCurrentDefined, current, throwException);
+ RETURN_IF_EXCEPTION(scope, false);
+ if (!isCompatibleDescriptor) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy's 'defineProperty' trap did not define a property on its target that is compatible with the trap's input descriptor"));
+ return false;
+ }
+ if (settingConfigurableToFalse && targetDescriptor.configurable()) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy's 'defineProperty' trap did not define a non-configurable property on its target even though the input descriptor to the trap said it must do so"));
+ return false;
+ }
+
+ return true;
+}
+
+bool ProxyObject::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow)
+{
+ ProxyObject* thisObject = jsCast<ProxyObject*>(object);
+ return thisObject->performDefineOwnProperty(exec, propertyName, descriptor, shouldThrow);
+}
+
+void ProxyObject::performGetOwnPropertyNames(ExecState* exec, PropertyNameArray& trapResult, EnumerationMode enumerationMode)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
+ throwStackOverflowError(exec, scope);
+ return;
+ }
+ JSValue handlerValue = this->handler();
+ if (handlerValue.isNull()) {
+ throwVMTypeError(exec, scope, ASCIILiteral(s_proxyAlreadyRevokedErrorMessage));
+ return;
+ }
+
+ JSObject* handler = jsCast<JSObject*>(handlerValue);
+ CallData callData;
+ CallType callType;
+ JSValue ownKeysMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "ownKeys"), ASCIILiteral("'ownKeys' property of a Proxy's handler should be callable"));
+ RETURN_IF_EXCEPTION(scope, void());
+ JSObject* target = this->target();
+ if (ownKeysMethod.isUndefined()) {
+ scope.release();
+ target->methodTable(vm)->getOwnPropertyNames(target, exec, trapResult, enumerationMode);
+ return;
+ }
+
+ MarkedArgumentBuffer arguments;
+ arguments.append(target);
+ JSValue arrayLikeObject = call(exec, ownKeysMethod, callType, callData, handler, arguments);
+ RETURN_IF_EXCEPTION(scope, void());
+
+ PropertyNameMode propertyNameMode = trapResult.mode();
+ RuntimeTypeMask resultFilter = 0;
+ switch (propertyNameMode) {
+ case PropertyNameMode::Symbols:
+ resultFilter = TypeSymbol;
+ break;
+ case PropertyNameMode::Strings:
+ resultFilter = TypeString;
+ break;
+ case PropertyNameMode::StringsAndSymbols:
+ resultFilter = TypeSymbol | TypeString;
+ break;
+ }
+ ASSERT(resultFilter);
+ RuntimeTypeMask dontThrowAnExceptionTypeFilter = TypeString | TypeSymbol;
+ HashSet<UniquedStringImpl*> uncheckedResultKeys;
+
+ auto addPropName = [&] (JSValue value, RuntimeType type) -> bool {
+ static const bool doExitEarly = true;
+ static const bool dontExitEarly = false;
+
+ if (!(type & resultFilter))
+ return dontExitEarly;
+
+ Identifier ident = value.toPropertyKey(exec);
+ RETURN_IF_EXCEPTION(scope, doExitEarly);
+
+ uncheckedResultKeys.add(ident.impl());
+ trapResult.addUnchecked(ident.impl());
+ return dontExitEarly;
+ };
+
+ createListFromArrayLike(exec, arrayLikeObject, dontThrowAnExceptionTypeFilter, ASCIILiteral("Proxy handler's 'ownKeys' method must return an array-like object containing only Strings and Symbols"), addPropName);
+ RETURN_IF_EXCEPTION(scope, void());
+
+ bool targetIsExensible = target->isExtensible(exec);
+ RETURN_IF_EXCEPTION(scope, void());
+
+ PropertyNameArray targetKeys(&vm, propertyNameMode);
+ target->methodTable(vm)->getOwnPropertyNames(target, exec, targetKeys, enumerationMode);
+ RETURN_IF_EXCEPTION(scope, void());
+ Vector<UniquedStringImpl*> targetConfigurableKeys;
+ Vector<UniquedStringImpl*> targetNonConfigurableKeys;
+ for (const Identifier& ident : targetKeys) {
+ PropertyDescriptor descriptor;
+ bool isPropertyDefined = target->getOwnPropertyDescriptor(exec, ident.impl(), descriptor);
+ RETURN_IF_EXCEPTION(scope, void());
+ if (isPropertyDefined && !descriptor.configurable())
+ targetNonConfigurableKeys.append(ident.impl());
+ else
+ targetConfigurableKeys.append(ident.impl());
+ }
+
+ enum ContainedIn { IsContainedIn, IsNotContainedIn };
+ auto removeIfContainedInUncheckedResultKeys = [&] (UniquedStringImpl* impl) -> ContainedIn {
+ auto iter = uncheckedResultKeys.find(impl);
+ if (iter == uncheckedResultKeys.end())
+ return IsNotContainedIn;
+
+ uncheckedResultKeys.remove(iter);
+ return IsContainedIn;
+ };
+
+ for (UniquedStringImpl* impl : targetNonConfigurableKeys) {
+ if (removeIfContainedInUncheckedResultKeys(impl) == IsNotContainedIn) {
+ throwVMTypeError(exec, scope, makeString("Proxy object's 'target' has the non-configurable property '", String(impl), "' that was not in the result from the 'ownKeys' trap"));
+ return;
+ }
+ }
+
+ if (targetIsExensible)
+ return;
+
+ for (UniquedStringImpl* impl : targetConfigurableKeys) {
+ if (removeIfContainedInUncheckedResultKeys(impl) == IsNotContainedIn) {
+ throwVMTypeError(exec, scope, makeString("Proxy object's non-extensible 'target' has configurable property '", String(impl), "' that was not in the result from the 'ownKeys' trap"));
+ return;
+ }
+ }
+
+ if (uncheckedResultKeys.size()) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy handler's 'ownKeys' method returned a key that was not present in its non-extensible target"));
+ return;
+ }
+}
+
+void ProxyObject::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNameArray, EnumerationMode enumerationMode)
+{
+ ProxyObject* thisObject = jsCast<ProxyObject*>(object);
+ thisObject->performGetOwnPropertyNames(exec, propertyNameArray, enumerationMode);
+}
+
+void ProxyObject::getPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNameArray, EnumerationMode enumerationMode)
+{
+ JSObject::getPropertyNames(object, exec, propertyNameArray, enumerationMode);
+}
+
+void ProxyObject::getOwnNonIndexPropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode)
+{
+ RELEASE_ASSERT_NOT_REACHED();
+}
+
+void ProxyObject::getStructurePropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode)
+{
+ // We should always go down the getOwnPropertyNames path.
+ RELEASE_ASSERT_NOT_REACHED();
+}
+
+void ProxyObject::getGenericPropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode)
+{
+ RELEASE_ASSERT_NOT_REACHED();
+}
+
+bool ProxyObject::performSetPrototype(ExecState* exec, JSValue prototype, bool shouldThrowIfCantSet)
+{
+ ASSERT(prototype.isObject() || prototype.isNull());
+
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
+ throwStackOverflowError(exec, scope);
+ return false;
+ }
+
+ JSValue handlerValue = this->handler();
+ if (handlerValue.isNull()) {
+ throwVMTypeError(exec, scope, ASCIILiteral(s_proxyAlreadyRevokedErrorMessage));
+ return false;
+ }
+
+ JSObject* handler = jsCast<JSObject*>(handlerValue);
+ CallData callData;
+ CallType callType;
+ JSValue setPrototypeOfMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "setPrototypeOf"), ASCIILiteral("'setPrototypeOf' property of a Proxy's handler should be callable"));
+ RETURN_IF_EXCEPTION(scope, false);
+
+ JSObject* target = this->target();
+ if (setPrototypeOfMethod.isUndefined()) {
+ scope.release();
+ return target->setPrototype(vm, exec, prototype, shouldThrowIfCantSet);
+ }
+
+ MarkedArgumentBuffer arguments;
+ arguments.append(target);
+ arguments.append(prototype);
+ JSValue trapResult = call(exec, setPrototypeOfMethod, callType, callData, handler, arguments);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ bool trapResultAsBool = trapResult.toBoolean(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+
+ if (!trapResultAsBool) {
+ if (shouldThrowIfCantSet)
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy 'setPrototypeOf' returned false indicating it could not set the prototype value. The operation was expected to succeed"));
+ return false;
+ }
+
+ bool targetIsExtensible = target->isExtensible(exec);
+ RETURN_IF_EXCEPTION(scope, false);
+ if (targetIsExtensible)
+ return true;
+
+ JSValue targetPrototype = target->getPrototype(vm, exec);
+ RETURN_IF_EXCEPTION(scope, false);
+ if (!sameValue(exec, prototype, targetPrototype)) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy 'setPrototypeOf' trap returned true when its target is non-extensible and the new prototype value is not the same as the current prototype value. It should have returned false"));
+ return false;
+ }
+
+ return true;
+}
+
+bool ProxyObject::setPrototype(JSObject* object, ExecState* exec, JSValue prototype, bool shouldThrowIfCantSet)
+{
+ return jsCast<ProxyObject*>(object)->performSetPrototype(exec, prototype, shouldThrowIfCantSet);
+}
+
+JSValue ProxyObject::performGetPrototype(ExecState* exec)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
+ throwStackOverflowError(exec, scope);
+ return { };
+ }
+
+ JSValue handlerValue = this->handler();
+ if (handlerValue.isNull()) {
+ throwVMTypeError(exec, scope, ASCIILiteral(s_proxyAlreadyRevokedErrorMessage));
+ return { };
+ }
+
+ JSObject* handler = jsCast<JSObject*>(handlerValue);
+ CallData callData;
+ CallType callType;
+ JSValue getPrototypeOfMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "getPrototypeOf"), ASCIILiteral("'getPrototypeOf' property of a Proxy's handler should be callable"));
+ RETURN_IF_EXCEPTION(scope, { });
+
+ JSObject* target = this->target();
+ if (getPrototypeOfMethod.isUndefined()) {
+ scope.release();
+ return target->getPrototype(vm, exec);
+ }
+
+ MarkedArgumentBuffer arguments;
+ arguments.append(target);
+ JSValue trapResult = call(exec, getPrototypeOfMethod, callType, callData, handler, arguments);
+ RETURN_IF_EXCEPTION(scope, { });
+
+ if (!trapResult.isObject() && !trapResult.isNull()) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy handler's 'getPrototypeOf' trap should either return an object or null"));
+ return { };
+ }
+
+ bool targetIsExtensible = target->isExtensible(exec);
+ RETURN_IF_EXCEPTION(scope, { });
+ if (targetIsExtensible)
+ return trapResult;
+
+ JSValue targetPrototype = target->getPrototype(vm, exec);
+ RETURN_IF_EXCEPTION(scope, { });
+ if (!sameValue(exec, targetPrototype, trapResult)) {
+ throwVMTypeError(exec, scope, ASCIILiteral("Proxy's 'getPrototypeOf' trap for a non-extensible target should return the same value as the target's prototype"));
+ return { };
+ }
+
+ return trapResult;
+}
+
+JSValue ProxyObject::getPrototype(JSObject* object, ExecState* exec)
+{
+ return jsCast<ProxyObject*>(object)->performGetPrototype(exec);
+}
+
+void ProxyObject::revoke(VM& vm)
+{
+ // This should only ever be called once and we should strictly transition from Object to null.
+ RELEASE_ASSERT(!m_handler.get().isNull() && m_handler.get().isObject());
+ m_handler.set(vm, this, jsNull());
+}
+
+bool ProxyObject::isRevoked() const
+{
+ return handler().isNull();
+}
+
+void ProxyObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
+{
+ ProxyObject* thisObject = jsCast<ProxyObject*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ Base::visitChildren(thisObject, visitor);
+
+ visitor.append(thisObject->m_target);
+ visitor.append(thisObject->m_handler);
+}
+
+} // namespace JSC