summaryrefslogtreecommitdiffstats
path: root/js/src/proxy/Proxy.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/proxy/Proxy.cpp')
-rw-r--r--js/src/proxy/Proxy.cpp1063
1 files changed, 1063 insertions, 0 deletions
diff --git a/js/src/proxy/Proxy.cpp b/js/src/proxy/Proxy.cpp
new file mode 100644
index 0000000000..6841691111
--- /dev/null
+++ b/js/src/proxy/Proxy.cpp
@@ -0,0 +1,1063 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "js/Proxy.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include <string.h>
+
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit, js::GetNativeStackLimit
+#include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowProxyIfWindow
+#include "js/PropertySpec.h"
+#include "js/Value.h" // JS::ObjectValue
+#include "js/Wrapper.h"
+#include "proxy/DeadObjectProxy.h"
+#include "proxy/ScriptedProxyHandler.h"
+#include "vm/Compartment.h"
+#include "vm/Interpreter.h" // js::CallGetter
+#include "vm/JSContext.h"
+#include "vm/JSFunction.h"
+#include "vm/JSObject.h"
+#include "vm/WrapperObject.h"
+
+#include "gc/Marking-inl.h"
+#include "vm/JSAtom-inl.h"
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+// Used by private fields to manipulate the ProxyExpando:
+// All the following methods are called iff the handler for the proxy
+// returns true for useProxyExpandoObjectForPrivateFields.
+static bool ProxySetOnExpando(JSContext* cx, HandleObject proxy, HandleId id,
+ HandleValue v, HandleValue receiver,
+ ObjectOpResult& result) {
+ MOZ_ASSERT(id.isPrivateName());
+
+ // For BaseProxyHandler, private names are stored in the expando object.
+ RootedObject expando(cx, proxy->as<ProxyObject>().expando().toObjectOrNull());
+
+ // SetPrivateElementOperation checks for hasOwn first, which ensures the
+ // expando exsists.
+ //
+ // If we don't have an expando, then we're probably misusing debugger apis and
+ // should just throw.
+ if (!expando) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_SET_MISSING_PRIVATE);
+ return false;
+ }
+
+ Rooted<mozilla::Maybe<PropertyDescriptor>> ownDesc(cx);
+ if (!GetOwnPropertyDescriptor(cx, expando, id, &ownDesc)) {
+ return false;
+ }
+ if (ownDesc.isNothing()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_SET_MISSING_PRIVATE);
+ return false;
+ }
+
+ RootedValue expandoValue(cx, proxy->as<ProxyObject>().expando());
+ return SetPropertyIgnoringNamedGetter(cx, expando, id, v, expandoValue,
+ ownDesc, result);
+}
+
+static bool ProxyGetOwnPropertyDescriptorFromExpando(
+ JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
+ RootedObject expando(cx, proxy->as<ProxyObject>().expando().toObjectOrNull());
+
+ if (!expando) {
+ return true;
+ }
+
+ return GetOwnPropertyDescriptor(cx, expando, id, desc);
+}
+
+static bool ProxyGetOnExpando(JSContext* cx, HandleObject proxy,
+ HandleValue receiver, HandleId id,
+ MutableHandleValue vp) {
+ // For BaseProxyHandler, private names are stored in the expando object.
+ RootedObject expando(cx, proxy->as<ProxyObject>().expando().toObjectOrNull());
+
+ // We must have the expando, or GetPrivateElemOperation didn't call
+ // hasPrivate first.
+ if (!expando) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_GET_MISSING_PRIVATE);
+ return false;
+ }
+
+ // Because we controlled the creation of the expando, we know it's not a
+ // proxy, and so can safely call internal methods on it without worrying about
+ // exposing information about private names.
+ Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
+ if (!GetOwnPropertyDescriptor(cx, expando, id, &desc)) {
+ return false;
+ }
+ // We must have the object, same reasoning as the expando.
+ if (desc.isNothing()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_SET_MISSING_PRIVATE);
+ return false;
+ }
+
+ // If the private name has a getter, delegate to that.
+ if (desc->hasGetter()) {
+ RootedValue getter(cx, JS::ObjectValue(*desc->getter()));
+ return js::CallGetter(cx, receiver, getter, vp);
+ }
+
+ MOZ_ASSERT(desc->hasValue());
+ MOZ_ASSERT(desc->isDataDescriptor());
+
+ vp.set(desc->value());
+ return true;
+}
+
+static bool ProxyHasOnExpando(JSContext* cx, HandleObject proxy, HandleId id,
+ bool* bp) {
+ // For BaseProxyHandler, private names are stored in the expando object.
+ RootedObject expando(cx, proxy->as<ProxyObject>().expando().toObjectOrNull());
+
+ // If there is no expando object, then there is no private field.
+ if (!expando) {
+ *bp = false;
+ return true;
+ }
+
+ return HasOwnProperty(cx, expando, id, bp);
+}
+
+static bool ProxyDefineOnExpando(JSContext* cx, HandleObject proxy, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) {
+ MOZ_ASSERT(id.isPrivateName());
+
+ // For BaseProxyHandler, private names are stored in the expando object.
+ RootedObject expando(cx, proxy->as<ProxyObject>().expando().toObjectOrNull());
+
+ if (!expando) {
+ expando = NewPlainObjectWithProto(cx, nullptr);
+ if (!expando) {
+ return false;
+ }
+
+ proxy->as<ProxyObject>().setExpando(expando);
+ }
+
+ return DefineProperty(cx, expando, id, desc, result);
+}
+
+void js::AutoEnterPolicy::reportErrorIfExceptionIsNotPending(JSContext* cx,
+ HandleId id) {
+ if (JS_IsExceptionPending(cx)) {
+ return;
+ }
+
+ if (id.isVoid()) {
+ ReportAccessDenied(cx);
+ } else {
+ Throw(cx, id, JSMSG_PROPERTY_ACCESS_DENIED);
+ }
+}
+
+#ifdef DEBUG
+void js::AutoEnterPolicy::recordEnter(JSContext* cx, HandleObject proxy,
+ HandleId id, Action act) {
+ if (allowed()) {
+ context = cx;
+ enteredProxy.emplace(proxy);
+ enteredId.emplace(id);
+ enteredAction = act;
+ prev = cx->enteredPolicy;
+ cx->enteredPolicy = this;
+ }
+}
+
+void js::AutoEnterPolicy::recordLeave() {
+ if (enteredProxy) {
+ MOZ_ASSERT(context->enteredPolicy == this);
+ context->enteredPolicy = prev;
+ }
+}
+
+JS_PUBLIC_API void js::assertEnteredPolicy(JSContext* cx, JSObject* proxy,
+ jsid id,
+ BaseProxyHandler::Action act) {
+ MOZ_ASSERT(proxy->is<ProxyObject>());
+ MOZ_ASSERT(cx->enteredPolicy);
+ MOZ_ASSERT(cx->enteredPolicy->enteredProxy->get() == proxy);
+ MOZ_ASSERT(cx->enteredPolicy->enteredId->get() == id);
+ MOZ_ASSERT(cx->enteredPolicy->enteredAction & act);
+}
+#endif
+
+bool Proxy::getOwnPropertyDescriptor(
+ JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ desc.reset(); // default result if we refuse to perform this action
+ AutoEnterPolicy policy(cx, handler, proxy, id,
+ BaseProxyHandler::GET_PROPERTY_DESCRIPTOR, true);
+ if (!policy.allowed()) {
+ return policy.returnValue();
+ }
+
+ if (handler->useProxyExpandoObjectForPrivateFields() && id.isPrivateName()) {
+ return ProxyGetOwnPropertyDescriptorFromExpando(cx, proxy, id, desc);
+ }
+ return handler->getOwnPropertyDescriptor(cx, proxy, id, desc);
+}
+
+bool Proxy::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+
+ // We shouldn't be definining a private field if we are supposed to throw;
+ // this ought to have been caught by CheckPrivateField.
+ MOZ_ASSERT_IF(id.isPrivateName(), !handler->throwOnPrivateField());
+
+ AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
+ if (!policy.allowed()) {
+ if (!policy.returnValue()) {
+ return false;
+ }
+ return result.succeed();
+ }
+
+ // Private field accesses have different semantics depending on the kind
+ // of proxy involved, and so take a different path compared to regular
+ // [[Get]] operations. For example, scripted handlers don't fire traps
+ // when accessing private fields (because of the WeakMap semantics)
+ if (id.isPrivateName() && handler->useProxyExpandoObjectForPrivateFields()) {
+ return ProxyDefineOnExpando(cx, proxy, id, desc, result);
+ }
+
+ return proxy->as<ProxyObject>().handler()->defineProperty(cx, proxy, id, desc,
+ result);
+}
+
+bool Proxy::ownPropertyKeys(JSContext* cx, HandleObject proxy,
+ MutableHandleIdVector props) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey,
+ BaseProxyHandler::ENUMERATE, true);
+ if (!policy.allowed()) {
+ return policy.returnValue();
+ }
+ return proxy->as<ProxyObject>().handler()->ownPropertyKeys(cx, proxy, props);
+}
+
+bool Proxy::delete_(JSContext* cx, HandleObject proxy, HandleId id,
+ ObjectOpResult& result) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
+ if (!policy.allowed()) {
+ bool ok = policy.returnValue();
+ if (ok) {
+ result.succeed();
+ }
+ return ok;
+ }
+
+ // Private names shouldn't take this path, as deleting a private name
+ // should be a syntax error.
+ MOZ_ASSERT(!id.isPrivateName());
+
+ return proxy->as<ProxyObject>().handler()->delete_(cx, proxy, id, result);
+}
+
+JS_PUBLIC_API bool js::AppendUnique(JSContext* cx, MutableHandleIdVector base,
+ HandleIdVector others) {
+ RootedIdVector uniqueOthers(cx);
+ if (!uniqueOthers.reserve(others.length())) {
+ return false;
+ }
+ for (size_t i = 0; i < others.length(); ++i) {
+ bool unique = true;
+ for (size_t j = 0; j < base.length(); ++j) {
+ if (others[i].get() == base[j]) {
+ unique = false;
+ break;
+ }
+ }
+ if (unique) {
+ if (!uniqueOthers.append(others[i])) {
+ return false;
+ }
+ }
+ }
+ return base.appendAll(std::move(uniqueOthers));
+}
+
+/* static */
+bool Proxy::getPrototype(JSContext* cx, HandleObject proxy,
+ MutableHandleObject proto) {
+ MOZ_ASSERT(proxy->hasDynamicPrototype());
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ return proxy->as<ProxyObject>().handler()->getPrototype(cx, proxy, proto);
+}
+
+/* static */
+bool Proxy::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
+ ObjectOpResult& result) {
+ MOZ_ASSERT(proxy->hasDynamicPrototype());
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ return proxy->as<ProxyObject>().handler()->setPrototype(cx, proxy, proto,
+ result);
+}
+
+/* static */
+bool Proxy::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy,
+ bool* isOrdinary,
+ MutableHandleObject proto) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ return proxy->as<ProxyObject>().handler()->getPrototypeIfOrdinary(
+ cx, proxy, isOrdinary, proto);
+}
+
+/* static */
+bool Proxy::setImmutablePrototype(JSContext* cx, HandleObject proxy,
+ bool* succeeded) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ return handler->setImmutablePrototype(cx, proxy, succeeded);
+}
+
+/* static */
+bool Proxy::preventExtensions(JSContext* cx, HandleObject proxy,
+ ObjectOpResult& result) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ return handler->preventExtensions(cx, proxy, result);
+}
+
+/* static */
+bool Proxy::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ return proxy->as<ProxyObject>().handler()->isExtensible(cx, proxy,
+ extensible);
+}
+
+bool Proxy::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ *bp = false; // default result if we refuse to perform this action
+ AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
+ if (!policy.allowed()) {
+ return policy.returnValue();
+ }
+
+ // Private names shouldn't take this path, but only hasOwn;
+ MOZ_ASSERT(!id.isPrivateName());
+
+ if (handler->hasPrototype()) {
+ if (!handler->hasOwn(cx, proxy, id, bp)) {
+ return false;
+ }
+ if (*bp) {
+ return true;
+ }
+
+ RootedObject proto(cx);
+ if (!GetPrototype(cx, proxy, &proto)) {
+ return false;
+ }
+ if (!proto) {
+ return true;
+ }
+
+ return HasProperty(cx, proto, id, bp);
+ }
+
+ return handler->has(cx, proxy, id, bp);
+}
+
+bool js::ProxyHas(JSContext* cx, HandleObject proxy, HandleValue idVal,
+ bool* result) {
+ RootedId id(cx);
+ if (!ToPropertyKey(cx, idVal, &id)) {
+ return false;
+ }
+
+ return Proxy::has(cx, proxy, id, result);
+}
+
+bool Proxy::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ *bp = false; // default result if we refuse to perform this action
+
+ // If the handler is supposed to throw, we'll never have a private field so
+ // simply return, as we shouldn't throw an invalid security error when
+ // checking for the presence of a private field (WeakMap model).
+ if (id.isPrivateName() && handler->throwOnPrivateField()) {
+ return true;
+ }
+
+ AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
+ if (!policy.allowed()) {
+ return policy.returnValue();
+ }
+
+ // Private field accesses have different semantics depending on the kind
+ // of proxy involved, and so take a different path compared to regular
+ // [[Get]] operations. For example, scripted handlers don't fire traps
+ // when accessing private fields (because of the WeakMap semantics)
+ if (id.isPrivateName() && handler->useProxyExpandoObjectForPrivateFields()) {
+ return ProxyHasOnExpando(cx, proxy, id, bp);
+ }
+
+ return handler->hasOwn(cx, proxy, id, bp);
+}
+
+bool js::ProxyHasOwn(JSContext* cx, HandleObject proxy, HandleValue idVal,
+ bool* result) {
+ RootedId id(cx);
+ if (!ToPropertyKey(cx, idVal, &id)) {
+ return false;
+ }
+
+ return Proxy::hasOwn(cx, proxy, id, result);
+}
+
+static MOZ_ALWAYS_INLINE Value ValueToWindowProxyIfWindow(const Value& v,
+ JSObject* proxy) {
+ if (v.isObject() && v != ObjectValue(*proxy)) {
+ return ObjectValue(*ToWindowProxyIfWindow(&v.toObject()));
+ }
+ return v;
+}
+
+MOZ_ALWAYS_INLINE bool Proxy::getInternal(JSContext* cx, HandleObject proxy,
+ HandleValue receiver, HandleId id,
+ MutableHandleValue vp) {
+ MOZ_ASSERT_IF(receiver.isObject(), !IsWindow(&receiver.toObject()));
+
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+
+ // Shouldn't have gotten here, as this should have been caught earlier.
+ MOZ_ASSERT_IF(id.isPrivateName(), !handler->throwOnPrivateField());
+
+ vp.setUndefined(); // default result if we refuse to perform this action
+ AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
+ if (!policy.allowed()) {
+ return policy.returnValue();
+ }
+
+ // Private field accesses have different semantics depending on the kind
+ // of proxy involved, and so take a different path compared to regular
+ // [[Get]] operations. For example, scripted handlers don't fire traps
+ // when accessing private fields (because of the WeakMap semantics)
+ if (id.isPrivateName() && handler->useProxyExpandoObjectForPrivateFields()) {
+ return ProxyGetOnExpando(cx, proxy, receiver, id, vp);
+ }
+
+ if (handler->hasPrototype()) {
+ bool own;
+ if (!handler->hasOwn(cx, proxy, id, &own)) {
+ return false;
+ }
+ if (!own) {
+ RootedObject proto(cx);
+ if (!GetPrototype(cx, proxy, &proto)) {
+ return false;
+ }
+ if (!proto) {
+ return true;
+ }
+ return GetProperty(cx, proto, receiver, id, vp);
+ }
+ }
+
+ return handler->get(cx, proxy, receiver, id, vp);
+}
+
+bool Proxy::get(JSContext* cx, HandleObject proxy, HandleValue receiver_,
+ HandleId id, MutableHandleValue vp) {
+ // Use the WindowProxy as receiver if receiver_ is a Window. Proxy handlers
+ // shouldn't have to know about the Window/WindowProxy distinction.
+ RootedValue receiver(cx, ValueToWindowProxyIfWindow(receiver_, proxy));
+ return getInternal(cx, proxy, receiver, id, vp);
+}
+
+bool js::ProxyGetProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandleValue vp) {
+ RootedValue receiver(cx, ObjectValue(*proxy));
+ return Proxy::getInternal(cx, proxy, receiver, id, vp);
+}
+
+bool js::ProxyGetPropertyByValue(JSContext* cx, HandleObject proxy,
+ HandleValue idVal, MutableHandleValue vp) {
+ RootedId id(cx);
+ if (!ToPropertyKey(cx, idVal, &id)) {
+ return false;
+ }
+
+ RootedValue receiver(cx, ObjectValue(*proxy));
+ return Proxy::getInternal(cx, proxy, receiver, id, vp);
+}
+
+MOZ_ALWAYS_INLINE bool Proxy::setInternal(JSContext* cx, HandleObject proxy,
+ HandleId id, HandleValue v,
+ HandleValue receiver,
+ ObjectOpResult& result) {
+ MOZ_ASSERT_IF(receiver.isObject(), !IsWindow(&receiver.toObject()));
+
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+
+ // Should have been handled already.
+ MOZ_ASSERT_IF(id.isPrivateName(), !handler->throwOnPrivateField());
+
+ AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
+ if (!policy.allowed()) {
+ if (!policy.returnValue()) {
+ return false;
+ }
+ return result.succeed();
+ }
+
+ // Private field accesses have different semantics depending on the kind
+ // of proxy involved, and so take a different path compared to regular
+ // [[Set]] operations.
+ //
+ // This doesn't interact with hasPrototype, as PrivateFields are always
+ // own propertiers, and so we never deal with prototype traversals.
+ if (id.isPrivateName() && handler->useProxyExpandoObjectForPrivateFields()) {
+ return ProxySetOnExpando(cx, proxy, id, v, receiver, result);
+ }
+
+ // Special case. See the comment on BaseProxyHandler::mHasPrototype.
+ if (handler->hasPrototype()) {
+ return handler->BaseProxyHandler::set(cx, proxy, id, v, receiver, result);
+ }
+
+ return handler->set(cx, proxy, id, v, receiver, result);
+}
+
+bool Proxy::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
+ HandleValue receiver_, ObjectOpResult& result) {
+ // Use the WindowProxy as receiver if receiver_ is a Window. Proxy handlers
+ // shouldn't have to know about the Window/WindowProxy distinction.
+ RootedValue receiver(cx, ValueToWindowProxyIfWindow(receiver_, proxy));
+ return setInternal(cx, proxy, id, v, receiver, result);
+}
+
+bool js::ProxySetProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ HandleValue val, bool strict) {
+ ObjectOpResult result;
+ RootedValue receiver(cx, ObjectValue(*proxy));
+ if (!Proxy::setInternal(cx, proxy, id, val, receiver, result)) {
+ return false;
+ }
+ return result.checkStrictModeError(cx, proxy, id, strict);
+}
+
+bool js::ProxySetPropertyByValue(JSContext* cx, HandleObject proxy,
+ HandleValue idVal, HandleValue val,
+ bool strict) {
+ RootedId id(cx);
+ if (!ToPropertyKey(cx, idVal, &id)) {
+ return false;
+ }
+
+ ObjectOpResult result;
+ RootedValue receiver(cx, ObjectValue(*proxy));
+ if (!Proxy::setInternal(cx, proxy, id, val, receiver, result)) {
+ return false;
+ }
+ return result.checkStrictModeError(cx, proxy, id, strict);
+}
+
+bool Proxy::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
+ MutableHandleIdVector props) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey,
+ BaseProxyHandler::ENUMERATE, true);
+ if (!policy.allowed()) {
+ return policy.returnValue();
+ }
+ return handler->getOwnEnumerablePropertyKeys(cx, proxy, props);
+}
+
+bool Proxy::enumerate(JSContext* cx, HandleObject proxy,
+ MutableHandleIdVector props) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ if (handler->hasPrototype()) {
+ if (!Proxy::getOwnEnumerablePropertyKeys(cx, proxy, props)) {
+ return false;
+ }
+
+ RootedObject proto(cx);
+ if (!GetPrototype(cx, proxy, &proto)) {
+ return false;
+ }
+ if (!proto) {
+ return true;
+ }
+
+ cx->check(proxy, proto);
+
+ RootedIdVector protoProps(cx);
+ if (!GetPropertyKeys(cx, proto, 0, &protoProps)) {
+ return false;
+ }
+ return AppendUnique(cx, props, protoProps);
+ }
+
+ AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey,
+ BaseProxyHandler::ENUMERATE, true);
+
+ // If the policy denies access but wants us to return true, we need
+ // to return an empty |props| list.
+ if (!policy.allowed()) {
+ MOZ_ASSERT(props.empty());
+ return policy.returnValue();
+ }
+
+ return handler->enumerate(cx, proxy, props);
+}
+
+bool Proxy::call(JSContext* cx, HandleObject proxy, const CallArgs& args) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+
+ // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we
+ // can only set our default value once we're sure that we're not calling the
+ // trap.
+ AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey,
+ BaseProxyHandler::CALL, true);
+ if (!policy.allowed()) {
+ args.rval().setUndefined();
+ return policy.returnValue();
+ }
+
+ return handler->call(cx, proxy, args);
+}
+
+bool Proxy::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+
+ // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we
+ // can only set our default value once we're sure that we're not calling the
+ // trap.
+ AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey,
+ BaseProxyHandler::CALL, true);
+ if (!policy.allowed()) {
+ args.rval().setUndefined();
+ return policy.returnValue();
+ }
+
+ return handler->construct(cx, proxy, args);
+}
+
+bool Proxy::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
+ const CallArgs& args) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ RootedObject proxy(cx, &args.thisv().toObject());
+ // Note - we don't enter a policy here because our security architecture
+ // guards against nativeCall by overriding the trap itself in the right
+ // circumstances.
+ return proxy->as<ProxyObject>().handler()->nativeCall(cx, test, impl, args);
+}
+
+bool Proxy::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ return proxy->as<ProxyObject>().handler()->getBuiltinClass(cx, proxy, cls);
+}
+
+bool Proxy::isArray(JSContext* cx, HandleObject proxy,
+ JS::IsArrayAnswer* answer) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ return proxy->as<ProxyObject>().handler()->isArray(cx, proxy, answer);
+}
+
+const char* Proxy::className(JSContext* cx, HandleObject proxy) {
+ // Check for unbounded recursion, but don't signal an error; className
+ // needs to be infallible.
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.checkDontReport(cx)) {
+ return "too much recursion";
+ }
+
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey,
+ BaseProxyHandler::GET, /* mayThrow = */ false);
+ // Do the safe thing if the policy rejects.
+ if (!policy.allowed()) {
+ return handler->BaseProxyHandler::className(cx, proxy);
+ }
+ return handler->className(cx, proxy);
+}
+
+JSString* Proxy::fun_toString(JSContext* cx, HandleObject proxy,
+ bool isToSource) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return nullptr;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey,
+ BaseProxyHandler::GET, /* mayThrow = */ false);
+ // Do the safe thing if the policy rejects.
+ if (!policy.allowed()) {
+ return handler->BaseProxyHandler::fun_toString(cx, proxy, isToSource);
+ }
+ return handler->fun_toString(cx, proxy, isToSource);
+}
+
+RegExpShared* Proxy::regexp_toShared(JSContext* cx, HandleObject proxy) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return nullptr;
+ }
+ return proxy->as<ProxyObject>().handler()->regexp_toShared(cx, proxy);
+}
+
+bool Proxy::boxedValue_unbox(JSContext* cx, HandleObject proxy,
+ MutableHandleValue vp) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ return proxy->as<ProxyObject>().handler()->boxedValue_unbox(cx, proxy, vp);
+}
+
+JSObject* const TaggedProto::LazyProto = reinterpret_cast<JSObject*>(0x1);
+
+/* static */
+bool Proxy::getElements(JSContext* cx, HandleObject proxy, uint32_t begin,
+ uint32_t end, ElementAdder* adder) {
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.check(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey,
+ BaseProxyHandler::GET,
+ /* mayThrow = */ true);
+ if (!policy.allowed()) {
+ if (policy.returnValue()) {
+ MOZ_ASSERT(!cx->isExceptionPending());
+ return js::GetElementsWithAdder(cx, proxy, proxy, begin, end, adder);
+ }
+ return false;
+ }
+ return handler->getElements(cx, proxy, begin, end, adder);
+}
+
+/* static */
+void Proxy::trace(JSTracer* trc, JSObject* proxy) {
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ handler->trace(trc, proxy);
+}
+
+static bool proxy_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
+ MutableHandleObject objp,
+ PropertyResult* propp) {
+ bool found;
+ if (!Proxy::has(cx, obj, id, &found)) {
+ return false;
+ }
+
+ if (found) {
+ propp->setProxyProperty();
+ objp.set(obj);
+ } else {
+ propp->setNotFound();
+ objp.set(nullptr);
+ }
+ return true;
+}
+
+static bool proxy_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id,
+ ObjectOpResult& result) {
+ if (!Proxy::delete_(cx, obj, id, result)) {
+ return false;
+ }
+ return SuppressDeletedProperty(cx, obj, id); // XXX is this necessary?
+}
+
+/* static */
+void ProxyObject::traceEdgeToTarget(JSTracer* trc, ProxyObject* obj) {
+ TraceCrossCompartmentEdge(trc, obj, obj->slotOfPrivate(), "proxy target");
+}
+
+#ifdef DEBUG
+static inline void CheckProxyIsInCCWMap(ProxyObject* proxy) {
+ if (proxy->zone()->isGCCompacting()) {
+ // Skip this check during compacting GC since objects' object groups may be
+ // forwarded. It's not impossible to make this work, but requires adding a
+ // parallel lookupWrapper() path for this one case.
+ return;
+ }
+
+ JSObject* referent = MaybeForwarded(proxy->target());
+ if (referent->compartment() != proxy->compartment()) {
+ // Assert that this proxy is tracked in the wrapper map. We maintain the
+ // invariant that the wrapped object is the key in the wrapper map.
+ ObjectWrapperMap::Ptr p = proxy->compartment()->lookupWrapper(referent);
+ MOZ_ASSERT(p);
+ MOZ_ASSERT(*p->value().unsafeGet() == proxy);
+ }
+}
+#endif
+
+/* static */
+void ProxyObject::trace(JSTracer* trc, JSObject* obj) {
+ ProxyObject* proxy = &obj->as<ProxyObject>();
+
+ TraceNullableEdge(trc, proxy->slotOfExpando(), "expando");
+
+#ifdef DEBUG
+ JSContext* cx = TlsContext.get();
+ if (cx && cx->isStrictProxyCheckingEnabled() && proxy->is<WrapperObject>()) {
+ CheckProxyIsInCCWMap(proxy);
+ }
+#endif
+
+ // Note: If you add new slots here, make sure to change
+ // nuke() to cope.
+
+ traceEdgeToTarget(trc, proxy);
+
+ size_t nreserved = proxy->numReservedSlots();
+ for (size_t i = 0; i < nreserved; i++) {
+ /*
+ * The GC can use the second reserved slot to link the cross compartment
+ * wrappers into a linked list, in which case we don't want to trace it.
+ */
+ if (proxy->is<CrossCompartmentWrapperObject>() &&
+ i == CrossCompartmentWrapperObject::GrayLinkReservedSlot) {
+ continue;
+ }
+ TraceEdge(trc, proxy->reservedSlotPtr(i), "proxy_reserved");
+ }
+
+ Proxy::trace(trc, obj);
+}
+
+static void proxy_Finalize(JS::GCContext* gcx, JSObject* obj) {
+ // Suppress a bogus warning about finalize().
+ JS::AutoSuppressGCAnalysis nogc;
+
+ MOZ_ASSERT(obj->is<ProxyObject>());
+ ProxyObject* proxy = &obj->as<ProxyObject>();
+ proxy->handler()->finalize(gcx, obj);
+
+ if (!proxy->usingInlineValueArray() && proxy->isTenured()) {
+ auto* valArray = js::detail::GetProxyDataLayout(obj)->values();
+ size_t size =
+ js::detail::ProxyValueArray::sizeOf(proxy->numReservedSlots());
+ gcx->free_(obj, valArray, size, MemoryUse::ProxyExternalValueArray);
+ }
+}
+
+size_t js::proxy_ObjectMoved(JSObject* obj, JSObject* old) {
+ ProxyObject& proxy = obj->as<ProxyObject>();
+
+ if (IsInsideNursery(old)) {
+ proxy.nurseryProxyTenured(&old->as<ProxyObject>());
+ }
+
+ return proxy.handler()->objectMoved(obj, old);
+}
+
+void ProxyObject::nurseryProxyTenured(ProxyObject* old) {
+ if (old->usingInlineValueArray()) {
+ setInlineValueArray();
+ return;
+ }
+
+ Nursery& nursery = runtimeFromMainThread()->gc.nursery();
+ nursery.removeMallocedBufferDuringMinorGC(data.values());
+
+ size_t size = detail::ProxyValueArray::sizeOf(numReservedSlots());
+ AddCellMemory(this, size, MemoryUse::ProxyExternalValueArray);
+}
+
+const JSClassOps js::ProxyClassOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ proxy_Finalize, // finalize
+ nullptr, // call
+ nullptr, // construct
+ ProxyObject::trace, // trace
+};
+
+const ClassExtension js::ProxyClassExtension = {
+ proxy_ObjectMoved, // objectMovedOp
+};
+
+const ObjectOps js::ProxyObjectOps = {
+ proxy_LookupProperty, // lookupProperty
+ Proxy::defineProperty, // defineProperty
+ Proxy::has, // hasProperty
+ Proxy::get, // getProperty
+ Proxy::set, // setProperty
+ Proxy::getOwnPropertyDescriptor, // getOwnPropertyDescriptor
+ proxy_DeleteProperty, // deleteProperty
+ Proxy::getElements, // getElements
+ Proxy::fun_toString, // funToString
+};
+
+static const JSFunctionSpec proxy_static_methods[] = {
+ JS_FN("revocable", proxy_revocable, 2, 0), JS_FS_END};
+
+static const ClassSpec ProxyClassSpec = {
+ GenericCreateConstructor<js::proxy, 2, gc::AllocKind::FUNCTION>, nullptr,
+ proxy_static_methods, nullptr};
+
+const JSClass js::ProxyClass = PROXY_CLASS_DEF_WITH_CLASS_SPEC(
+ "Proxy",
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy) | JSCLASS_HAS_RESERVED_SLOTS(2),
+ &ProxyClassSpec);
+
+JS_PUBLIC_API JSObject* js::NewProxyObject(JSContext* cx,
+ const BaseProxyHandler* handler,
+ HandleValue priv, JSObject* proto_,
+ const ProxyOptions& options) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+
+ // This can be called from the compartment wrap hooks while in a realm with a
+ // gray global. Trigger the read barrier on the global to ensure this is
+ // unmarked.
+ cx->realm()->maybeGlobal();
+
+ if (proto_ != TaggedProto::LazyProto) {
+ cx->check(proto_); // |priv| might be cross-compartment.
+ }
+
+ if (options.lazyProto()) {
+ MOZ_ASSERT(!proto_);
+ proto_ = TaggedProto::LazyProto;
+ }
+
+ return ProxyObject::New(cx, handler, priv, TaggedProto(proto_),
+ options.clasp());
+}
+
+void ProxyObject::renew(const BaseProxyHandler* handler, const Value& priv) {
+ MOZ_ASSERT_IF(IsCrossCompartmentWrapper(this), IsDeadProxyObject(this));
+ MOZ_ASSERT(getClass() == &ProxyClass);
+ MOZ_ASSERT(!IsWindowProxy(this));
+ MOZ_ASSERT(hasDynamicPrototype());
+
+ setHandler(handler);
+ setCrossCompartmentPrivate(priv);
+ for (size_t i = 0; i < numReservedSlots(); i++) {
+ setReservedSlot(i, UndefinedValue());
+ }
+}
+
+// This implementation of HostEnsureCanAddPrivateElement is designed to work in
+// collaboration with Gecko to support the HTML implementation, which applies
+// only to Proxy type objects, and as a result we can simply provide proxy
+// handlers to correctly match the required semantics.
+bool DefaultHostEnsureCanAddPrivateElementCallback(JSContext* cx,
+ HandleValue val) {
+ if (!val.isObject()) {
+ return true;
+ }
+
+ Rooted<JSObject*> valObj(cx, &val.toObject());
+ if (!IsProxy(valObj)) {
+ return true;
+ }
+
+ if (GetProxyHandler(valObj)->throwOnPrivateField()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_ILLEGAL_PRIVATE_EXOTIC);
+ return false;
+ }
+ return true;
+}