summaryrefslogtreecommitdiffstats
path: root/js/src/proxy
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/proxy
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/proxy')
-rw-r--r--js/src/proxy/BaseProxyHandler.cpp420
-rw-r--r--js/src/proxy/CrossCompartmentWrapper.cpp676
-rw-r--r--js/src/proxy/DOMProxy.cpp41
-rw-r--r--js/src/proxy/DOMProxy.h34
-rw-r--r--js/src/proxy/DeadObjectProxy.cpp178
-rw-r--r--js/src/proxy/DeadObjectProxy.h107
-rw-r--r--js/src/proxy/OpaqueCrossCompartmentWrapper.cpp159
-rw-r--r--js/src/proxy/Proxy.cpp974
-rw-r--r--js/src/proxy/Proxy.h114
-rw-r--r--js/src/proxy/ScriptedProxyHandler.cpp1580
-rw-r--r--js/src/proxy/ScriptedProxyHandler.h116
-rw-r--r--js/src/proxy/SecurityWrapper.cpp110
-rw-r--r--js/src/proxy/Wrapper.cpp473
13 files changed, 4982 insertions, 0 deletions
diff --git a/js/src/proxy/BaseProxyHandler.cpp b/js/src/proxy/BaseProxyHandler.cpp
new file mode 100644
index 0000000000..66574cc12f
--- /dev/null
+++ b/js/src/proxy/BaseProxyHandler.cpp
@@ -0,0 +1,420 @@
+/* -*- 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 "NamespaceImports.h"
+
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/Proxy.h"
+#include "proxy/DeadObjectProxy.h"
+#include "vm/ProxyObject.h"
+#include "vm/WrapperObject.h"
+
+#include "vm/JSContext-inl.h"
+#include "vm/JSObject-inl.h"
+
+using namespace js;
+
+using JS::IsArrayAnswer;
+
+bool BaseProxyHandler::enter(JSContext* cx, HandleObject wrapper, HandleId id,
+ Action act, bool mayThrow, bool* bp) const {
+ *bp = true;
+ return true;
+}
+
+bool BaseProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id,
+ bool* bp) const {
+ assertEnteredPolicy(cx, proxy, id, GET);
+
+ // This method is not covered by any spec, but we follow ES 2016
+ // (February 11, 2016) 9.1.7.1 fairly closely.
+
+ // Step 2. (Step 1 is a superfluous assertion.)
+ // Non-standard: Use our faster hasOwn trap.
+ if (!hasOwn(cx, proxy, id, bp)) {
+ return false;
+ }
+
+ // Step 3.
+ if (*bp) {
+ return true;
+ }
+
+ // The spec calls this variable "parent", but that word has weird
+ // connotations in SpiderMonkey, so let's go with "proto".
+ // Step 4.
+ RootedObject proto(cx);
+ if (!GetPrototype(cx, proxy, &proto)) {
+ return false;
+ }
+
+ // Step 5.,5.a.
+ if (proto) {
+ return HasProperty(cx, proto, id, bp);
+ }
+
+ // Step 6.
+ *bp = false;
+ return true;
+}
+
+bool BaseProxyHandler::hasOwn(JSContext* cx, HandleObject proxy, HandleId id,
+ bool* bp) const {
+ assertEnteredPolicy(cx, proxy, id, GET);
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) {
+ return false;
+ }
+ *bp = !!desc.object();
+ return true;
+}
+
+bool BaseProxyHandler::get(JSContext* cx, HandleObject proxy,
+ HandleValue receiver, HandleId id,
+ MutableHandleValue vp) const {
+ assertEnteredPolicy(cx, proxy, id, GET);
+
+ // This method is not covered by any spec, but we follow ES 2016
+ // (January 21, 2016) 9.1.8 fairly closely.
+
+ // Step 2. (Step 1 is a superfluous assertion.)
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) {
+ return false;
+ }
+ desc.assertCompleteIfFound();
+
+ // Step 3.
+ if (!desc.object()) {
+ // The spec calls this variable "parent", but that word has weird
+ // connotations in SpiderMonkey, so let's go with "proto".
+ // Step 3.a.
+ RootedObject proto(cx);
+ if (!GetPrototype(cx, proxy, &proto)) {
+ return false;
+ }
+
+ // Step 3.b.
+ if (!proto) {
+ vp.setUndefined();
+ return true;
+ }
+
+ // Step 3.c.
+ return GetProperty(cx, proto, receiver, id, vp);
+ }
+
+ // Step 4.
+ if (desc.isDataDescriptor()) {
+ vp.set(desc.value());
+ return true;
+ }
+
+ // Step 5.
+ MOZ_ASSERT(desc.isAccessorDescriptor());
+ RootedObject getter(cx, desc.getterObject());
+
+ // Step 6.
+ if (!getter) {
+ vp.setUndefined();
+ return true;
+ }
+
+ // Step 7.
+ RootedValue getterFunc(cx, ObjectValue(*getter));
+ return CallGetter(cx, receiver, getterFunc, vp);
+}
+
+bool BaseProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id,
+ HandleValue v, HandleValue receiver,
+ ObjectOpResult& result) const {
+ assertEnteredPolicy(cx, proxy, id, SET);
+
+ // This method is not covered by any spec, but we follow ES6 draft rev 28
+ // (2014 Oct 14) 9.1.9 fairly closely, adapting it slightly for
+ // SpiderMonkey's particular foibles.
+
+ // Steps 2-3. (Step 1 is a superfluous assertion.)
+ Rooted<PropertyDescriptor> ownDesc(cx);
+ if (!getOwnPropertyDescriptor(cx, proxy, id, &ownDesc)) {
+ return false;
+ }
+ ownDesc.assertCompleteIfFound();
+
+ // The rest is factored out into a separate function with a weird name.
+ // This algorithm continues just below.
+ return SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, ownDesc,
+ result);
+}
+
+bool js::SetPropertyIgnoringNamedGetter(JSContext* cx, HandleObject obj,
+ HandleId id, HandleValue v,
+ HandleValue receiver,
+ Handle<PropertyDescriptor> ownDesc_,
+ ObjectOpResult& result) {
+ Rooted<PropertyDescriptor> ownDesc(cx, ownDesc_);
+
+ // Step 4.
+ if (!ownDesc.object()) {
+ // The spec calls this variable "parent", but that word has weird
+ // connotations in SpiderMonkey, so let's go with "proto".
+ RootedObject proto(cx);
+ if (!GetPrototype(cx, obj, &proto)) {
+ return false;
+ }
+ if (proto) {
+ return SetProperty(cx, proto, id, v, receiver, result);
+ }
+
+ // Step 4.d.
+ ownDesc.setDataDescriptor(UndefinedHandleValue, JSPROP_ENUMERATE);
+ }
+
+ // Step 5.
+ if (ownDesc.isDataDescriptor()) {
+ // Steps 5.a-b.
+ if (!ownDesc.writable()) {
+ return result.fail(JSMSG_READ_ONLY);
+ }
+ if (!receiver.isObject()) {
+ return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
+ }
+ RootedObject receiverObj(cx, &receiver.toObject());
+
+ // Nonstandard SpiderMonkey special case: setter ops.
+ if (SetterOp setter = ownDesc.setter()) {
+ return CallJSSetterOp(cx, setter, receiverObj, id, v, result);
+ }
+
+ // Steps 5.c-d.
+ Rooted<PropertyDescriptor> existingDescriptor(cx);
+ if (!GetOwnPropertyDescriptor(cx, receiverObj, id, &existingDescriptor)) {
+ return false;
+ }
+
+ // Step 5.e.
+ if (existingDescriptor.object()) {
+ // Step 5.e.i.
+ if (existingDescriptor.isAccessorDescriptor()) {
+ return result.fail(JSMSG_OVERWRITING_ACCESSOR);
+ }
+
+ // Step 5.e.ii.
+ if (!existingDescriptor.writable()) {
+ return result.fail(JSMSG_READ_ONLY);
+ }
+ }
+
+ // Steps 5.e.iii-iv. and 5.f.i.
+ unsigned attrs = existingDescriptor.object()
+ ? JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY |
+ JSPROP_IGNORE_PERMANENT
+ : JSPROP_ENUMERATE;
+
+ return DefineDataProperty(cx, receiverObj, id, v, attrs, result);
+ }
+
+ // Step 6.
+ MOZ_ASSERT(ownDesc.isAccessorDescriptor());
+ RootedObject setter(cx);
+ if (ownDesc.hasSetterObject()) {
+ setter = ownDesc.setterObject();
+ }
+ if (!setter) {
+ return result.fail(JSMSG_GETTER_ONLY);
+ }
+ RootedValue setterValue(cx, ObjectValue(*setter));
+ if (!CallSetter(cx, receiver, setterValue, v)) {
+ return false;
+ }
+ return result.succeed();
+}
+
+bool BaseProxyHandler::getOwnEnumerablePropertyKeys(
+ JSContext* cx, HandleObject proxy, MutableHandleIdVector props) const {
+ assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
+ MOZ_ASSERT(props.length() == 0);
+
+ if (!ownPropertyKeys(cx, proxy, props)) {
+ return false;
+ }
+
+ /* Select only the enumerable properties through in-place iteration. */
+ RootedId id(cx);
+ size_t i = 0;
+ for (size_t j = 0, len = props.length(); j < len; j++) {
+ MOZ_ASSERT(i <= j);
+ id = props[j];
+ if (JSID_IS_SYMBOL(id)) {
+ continue;
+ }
+
+ AutoWaivePolicy policy(cx, proxy, id, BaseProxyHandler::GET);
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) {
+ return false;
+ }
+ desc.assertCompleteIfFound();
+
+ if (desc.object() && desc.enumerable()) {
+ props[i++].set(id);
+ }
+ }
+
+ MOZ_ASSERT(i <= props.length());
+ if (!props.resize(i)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool BaseProxyHandler::enumerate(JSContext* cx, HandleObject proxy,
+ MutableHandleIdVector props) const {
+ assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
+
+ // GetPropertyKeys will invoke getOwnEnumerablePropertyKeys along the proto
+ // chain for us.
+ MOZ_ASSERT(props.empty());
+ return GetPropertyKeys(cx, proxy, 0, props);
+}
+
+bool BaseProxyHandler::call(JSContext* cx, HandleObject proxy,
+ const CallArgs& args) const {
+ MOZ_CRASH("callable proxies should implement call trap");
+}
+
+bool BaseProxyHandler::construct(JSContext* cx, HandleObject proxy,
+ const CallArgs& args) const {
+ MOZ_CRASH("callable proxies should implement construct trap");
+}
+
+const char* BaseProxyHandler::className(JSContext* cx,
+ HandleObject proxy) const {
+ return proxy->isCallable() ? "Function" : "Object";
+}
+
+JSString* BaseProxyHandler::fun_toString(JSContext* cx, HandleObject proxy,
+ bool isToSource) const {
+ if (proxy->isCallable()) {
+ return JS_NewStringCopyZ(cx, "function () {\n [native code]\n}");
+ }
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_INCOMPATIBLE_PROTO, js_Function_str,
+ js_toString_str, "object");
+ return nullptr;
+}
+
+RegExpShared* BaseProxyHandler::regexp_toShared(JSContext* cx,
+ HandleObject proxy) const {
+ MOZ_CRASH("This should have been a wrapped regexp");
+}
+
+bool BaseProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy,
+ MutableHandleValue vp) const {
+ vp.setUndefined();
+ return true;
+}
+
+bool BaseProxyHandler::nativeCall(JSContext* cx, IsAcceptableThis test,
+ NativeImpl impl, const CallArgs& args) const {
+ ReportIncompatible(cx, args);
+ return false;
+}
+
+bool BaseProxyHandler::hasInstance(JSContext* cx, HandleObject proxy,
+ MutableHandleValue v, bool* bp) const {
+ assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
+ cx->check(proxy, v);
+ return JS::InstanceofOperator(cx, proxy, v, bp);
+}
+
+bool BaseProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
+ ESClass* cls) const {
+ *cls = ESClass::Other;
+ return true;
+}
+
+bool BaseProxyHandler::isArray(JSContext* cx, HandleObject proxy,
+ IsArrayAnswer* answer) const {
+ *answer = IsArrayAnswer::NotArray;
+ return true;
+}
+
+void BaseProxyHandler::trace(JSTracer* trc, JSObject* proxy) const {}
+
+void BaseProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const {}
+
+size_t BaseProxyHandler::objectMoved(JSObject* proxy, JSObject* old) const {
+ return 0;
+}
+
+bool BaseProxyHandler::getPrototype(JSContext* cx, HandleObject proxy,
+ MutableHandleObject protop) const {
+ MOZ_CRASH("must override getPrototype with dynamic prototype");
+}
+
+bool BaseProxyHandler::setPrototype(JSContext* cx, HandleObject proxy,
+ HandleObject proto,
+ ObjectOpResult& result) const {
+ // Disallow sets of protos on proxies with dynamic prototypes but no hook.
+ // This keeps us away from the footgun of having the first proto set opt
+ // you out of having dynamic protos altogether.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_CANT_SET_PROTO_OF, "incompatible Proxy");
+ return false;
+}
+
+bool BaseProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy,
+ bool* succeeded) const {
+ *succeeded = false;
+ return true;
+}
+
+bool BaseProxyHandler::getElements(JSContext* cx, HandleObject proxy,
+ uint32_t begin, uint32_t end,
+ ElementAdder* adder) const {
+ assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
+
+ return js::GetElementsWithAdder(cx, proxy, proxy, begin, end, adder);
+}
+
+bool BaseProxyHandler::isCallable(JSObject* obj) const { return false; }
+
+bool BaseProxyHandler::isConstructor(JSObject* obj) const { return false; }
+
+JS_FRIEND_API void js::NukeNonCCWProxy(JSContext* cx, HandleObject proxy) {
+ MOZ_ASSERT(proxy->is<ProxyObject>());
+ MOZ_ASSERT(!proxy->is<CrossCompartmentWrapperObject>());
+
+ // (NotifyGCNukeWrapper() only needs to be called on CCWs.)
+
+ // The proxy is about to be replaced, so we need to do any necessary
+ // cleanup first.
+ proxy->as<ProxyObject>().handler()->finalize(cx->defaultFreeOp(), proxy);
+
+ proxy->as<ProxyObject>().nuke();
+
+ MOZ_ASSERT(IsDeadProxyObject(proxy));
+}
+
+JS_FRIEND_API void js::NukeRemovedCrossCompartmentWrapper(JSContext* cx,
+ JSObject* wrapper) {
+ MOZ_ASSERT(wrapper->is<CrossCompartmentWrapperObject>());
+
+ NotifyGCNukeWrapper(wrapper);
+
+ // We don't need to call finalize here because the CCW finalizer doesn't do
+ // anything. Skipping finalize means that |wrapper| doesn't need to be rooted
+ // to pass the hazard analysis, which is needed because this method is called
+ // from some tricky places inside transplanting where rooting can be
+ // difficult.
+
+ wrapper->as<ProxyObject>().nuke();
+
+ MOZ_ASSERT(IsDeadProxyObject(wrapper));
+}
diff --git a/js/src/proxy/CrossCompartmentWrapper.cpp b/js/src/proxy/CrossCompartmentWrapper.cpp
new file mode 100644
index 0000000000..407864b023
--- /dev/null
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -0,0 +1,676 @@
+/* -*- 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 "gc/PublicIterators.h"
+#include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy
+#include "js/Wrapper.h"
+#include "proxy/DeadObjectProxy.h"
+#include "proxy/DOMProxy.h"
+#include "vm/Iteration.h"
+#include "vm/Runtime.h"
+#include "vm/WrapperObject.h"
+
+#include "gc/Nursery-inl.h"
+#include "vm/Compartment-inl.h"
+#include "vm/JSObject-inl.h"
+#include "vm/Realm-inl.h"
+
+using namespace js;
+
+#define PIERCE(cx, wrapper, pre, op, post) \
+ JS_BEGIN_MACRO \
+ bool ok; \
+ { \
+ AutoRealm call(cx, wrappedObject(wrapper)); \
+ ok = (pre) && (op); \
+ } \
+ return ok && (post); \
+ JS_END_MACRO
+
+#define NOTHING (true)
+
+static bool MarkAtoms(JSContext* cx, jsid id) {
+ cx->markId(id);
+ return true;
+}
+
+static bool MarkAtoms(JSContext* cx, HandleIdVector ids) {
+ for (size_t i = 0; i < ids.length(); i++) {
+ cx->markId(ids[i]);
+ }
+ return true;
+}
+
+bool CrossCompartmentWrapper::getOwnPropertyDescriptor(
+ JSContext* cx, HandleObject wrapper, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const {
+ PIERCE(cx, wrapper, MarkAtoms(cx, id),
+ Wrapper::getOwnPropertyDescriptor(cx, wrapper, id, desc),
+ cx->compartment()->wrap(cx, desc));
+}
+
+bool CrossCompartmentWrapper::defineProperty(JSContext* cx,
+ HandleObject wrapper, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const {
+ Rooted<PropertyDescriptor> desc2(cx, desc);
+ PIERCE(cx, wrapper, MarkAtoms(cx, id) && cx->compartment()->wrap(cx, &desc2),
+ Wrapper::defineProperty(cx, wrapper, id, desc2, result), NOTHING);
+}
+
+bool CrossCompartmentWrapper::ownPropertyKeys(
+ JSContext* cx, HandleObject wrapper, MutableHandleIdVector props) const {
+ PIERCE(cx, wrapper, NOTHING, Wrapper::ownPropertyKeys(cx, wrapper, props),
+ MarkAtoms(cx, props));
+}
+
+bool CrossCompartmentWrapper::delete_(JSContext* cx, HandleObject wrapper,
+ HandleId id,
+ ObjectOpResult& result) const {
+ PIERCE(cx, wrapper, MarkAtoms(cx, id),
+ Wrapper::delete_(cx, wrapper, id, result), NOTHING);
+}
+
+bool CrossCompartmentWrapper::getPrototype(JSContext* cx, HandleObject wrapper,
+ MutableHandleObject protop) const {
+ {
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+ AutoRealm call(cx, wrapped);
+ if (!GetPrototype(cx, wrapped, protop)) {
+ return false;
+ }
+ if (protop) {
+ if (!JSObject::setDelegate(cx, protop)) {
+ return false;
+ }
+ }
+ }
+
+ return cx->compartment()->wrap(cx, protop);
+}
+
+bool CrossCompartmentWrapper::setPrototype(JSContext* cx, HandleObject wrapper,
+ HandleObject proto,
+ ObjectOpResult& result) const {
+ RootedObject protoCopy(cx, proto);
+ PIERCE(cx, wrapper, cx->compartment()->wrap(cx, &protoCopy),
+ Wrapper::setPrototype(cx, wrapper, protoCopy, result), NOTHING);
+}
+
+bool CrossCompartmentWrapper::getPrototypeIfOrdinary(
+ JSContext* cx, HandleObject wrapper, bool* isOrdinary,
+ MutableHandleObject protop) const {
+ {
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+ AutoRealm call(cx, wrapped);
+ if (!GetPrototypeIfOrdinary(cx, wrapped, isOrdinary, protop)) {
+ return false;
+ }
+
+ if (!*isOrdinary) {
+ return true;
+ }
+
+ if (protop) {
+ if (!JSObject::setDelegate(cx, protop)) {
+ return false;
+ }
+ }
+ }
+
+ return cx->compartment()->wrap(cx, protop);
+}
+
+bool CrossCompartmentWrapper::setImmutablePrototype(JSContext* cx,
+ HandleObject wrapper,
+ bool* succeeded) const {
+ PIERCE(cx, wrapper, NOTHING,
+ Wrapper::setImmutablePrototype(cx, wrapper, succeeded), NOTHING);
+}
+
+bool CrossCompartmentWrapper::preventExtensions(JSContext* cx,
+ HandleObject wrapper,
+ ObjectOpResult& result) const {
+ PIERCE(cx, wrapper, NOTHING, Wrapper::preventExtensions(cx, wrapper, result),
+ NOTHING);
+}
+
+bool CrossCompartmentWrapper::isExtensible(JSContext* cx, HandleObject wrapper,
+ bool* extensible) const {
+ PIERCE(cx, wrapper, NOTHING, Wrapper::isExtensible(cx, wrapper, extensible),
+ NOTHING);
+}
+
+bool CrossCompartmentWrapper::has(JSContext* cx, HandleObject wrapper,
+ HandleId id, bool* bp) const {
+ PIERCE(cx, wrapper, MarkAtoms(cx, id), Wrapper::has(cx, wrapper, id, bp),
+ NOTHING);
+}
+
+bool CrossCompartmentWrapper::hasOwn(JSContext* cx, HandleObject wrapper,
+ HandleId id, bool* bp) const {
+ PIERCE(cx, wrapper, MarkAtoms(cx, id), Wrapper::hasOwn(cx, wrapper, id, bp),
+ NOTHING);
+}
+
+static bool WrapReceiver(JSContext* cx, HandleObject wrapper,
+ MutableHandleValue receiver) {
+ // Usually the receiver is the wrapper and we can just unwrap it. If the
+ // wrapped object is also a wrapper, things are more complicated and we
+ // fall back to the slow path (it calls UncheckedUnwrap to unwrap all
+ // wrappers).
+ if (ObjectValue(*wrapper) == receiver) {
+ JSObject* wrapped = Wrapper::wrappedObject(wrapper);
+ if (!IsWrapper(wrapped)) {
+ MOZ_ASSERT(wrapped->compartment() == cx->compartment());
+ MOZ_ASSERT(!IsWindow(wrapped));
+ receiver.setObject(*wrapped);
+ return true;
+ }
+ }
+
+ return cx->compartment()->wrap(cx, receiver);
+}
+
+bool CrossCompartmentWrapper::get(JSContext* cx, HandleObject wrapper,
+ HandleValue receiver, HandleId id,
+ MutableHandleValue vp) const {
+ RootedValue receiverCopy(cx, receiver);
+ {
+ AutoRealm call(cx, wrappedObject(wrapper));
+ if (!MarkAtoms(cx, id) || !WrapReceiver(cx, wrapper, &receiverCopy)) {
+ return false;
+ }
+
+ if (!Wrapper::get(cx, wrapper, receiverCopy, id, vp)) {
+ return false;
+ }
+ }
+ return cx->compartment()->wrap(cx, vp);
+}
+
+bool CrossCompartmentWrapper::set(JSContext* cx, HandleObject wrapper,
+ HandleId id, HandleValue v,
+ HandleValue receiver,
+ ObjectOpResult& result) const {
+ RootedValue valCopy(cx, v);
+ RootedValue receiverCopy(cx, receiver);
+ PIERCE(cx, wrapper,
+ MarkAtoms(cx, id) && cx->compartment()->wrap(cx, &valCopy) &&
+ WrapReceiver(cx, wrapper, &receiverCopy),
+ Wrapper::set(cx, wrapper, id, valCopy, receiverCopy, result), NOTHING);
+}
+
+bool CrossCompartmentWrapper::getOwnEnumerablePropertyKeys(
+ JSContext* cx, HandleObject wrapper, MutableHandleIdVector props) const {
+ PIERCE(cx, wrapper, NOTHING,
+ Wrapper::getOwnEnumerablePropertyKeys(cx, wrapper, props),
+ MarkAtoms(cx, props));
+}
+
+bool CrossCompartmentWrapper::enumerate(JSContext* cx, HandleObject wrapper,
+ MutableHandleIdVector props) const {
+ PIERCE(cx, wrapper, NOTHING, Wrapper::enumerate(cx, wrapper, props),
+ MarkAtoms(cx, props));
+}
+
+bool CrossCompartmentWrapper::call(JSContext* cx, HandleObject wrapper,
+ const CallArgs& args) const {
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+
+ {
+ AutoRealm call(cx, wrapped);
+
+ args.setCallee(ObjectValue(*wrapped));
+ if (!cx->compartment()->wrap(cx, args.mutableThisv())) {
+ return false;
+ }
+
+ for (size_t n = 0; n < args.length(); ++n) {
+ if (!cx->compartment()->wrap(cx, args[n])) {
+ return false;
+ }
+ }
+
+ if (!Wrapper::call(cx, wrapper, args)) {
+ return false;
+ }
+ }
+
+ return cx->compartment()->wrap(cx, args.rval());
+}
+
+bool CrossCompartmentWrapper::construct(JSContext* cx, HandleObject wrapper,
+ const CallArgs& args) const {
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+ {
+ AutoRealm call(cx, wrapped);
+
+ for (size_t n = 0; n < args.length(); ++n) {
+ if (!cx->compartment()->wrap(cx, args[n])) {
+ return false;
+ }
+ }
+ if (!cx->compartment()->wrap(cx, args.newTarget())) {
+ return false;
+ }
+ if (!Wrapper::construct(cx, wrapper, args)) {
+ return false;
+ }
+ }
+ return cx->compartment()->wrap(cx, args.rval());
+}
+
+bool CrossCompartmentWrapper::nativeCall(JSContext* cx, IsAcceptableThis test,
+ NativeImpl impl,
+ const CallArgs& srcArgs) const {
+ RootedObject wrapper(cx, &srcArgs.thisv().toObject());
+ MOZ_ASSERT(srcArgs.thisv().isMagic(JS_IS_CONSTRUCTING) ||
+ !UncheckedUnwrap(wrapper)->is<CrossCompartmentWrapperObject>());
+
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+ {
+ AutoRealm call(cx, wrapped);
+ InvokeArgs dstArgs(cx);
+ if (!dstArgs.init(cx, srcArgs.length())) {
+ return false;
+ }
+
+ Value* src = srcArgs.base();
+ Value* srcend = srcArgs.array() + srcArgs.length();
+ Value* dst = dstArgs.base();
+
+ RootedValue source(cx);
+ for (; src < srcend; ++src, ++dst) {
+ source = *src;
+ if (!cx->compartment()->wrap(cx, &source)) {
+ return false;
+ }
+ *dst = source.get();
+
+ // Handle |this| specially. When we rewrap on the other side of the
+ // membrane, we might apply a same-compartment security wrapper that
+ // will stymie this whole process. If that happens, unwrap the wrapper.
+ // This logic can go away when same-compartment security wrappers go away.
+ if ((src == srcArgs.base() + 1) && dst->isObject()) {
+ RootedObject thisObj(cx, &dst->toObject());
+ if (thisObj->is<WrapperObject>() &&
+ Wrapper::wrapperHandler(thisObj)->hasSecurityPolicy()) {
+ MOZ_ASSERT(!thisObj->is<CrossCompartmentWrapperObject>());
+ *dst = ObjectValue(*Wrapper::wrappedObject(thisObj));
+ }
+ }
+ }
+
+ if (!CallNonGenericMethod(cx, test, impl, dstArgs)) {
+ return false;
+ }
+
+ srcArgs.rval().set(dstArgs.rval());
+ }
+ return cx->compartment()->wrap(cx, srcArgs.rval());
+}
+
+bool CrossCompartmentWrapper::hasInstance(JSContext* cx, HandleObject wrapper,
+ MutableHandleValue v,
+ bool* bp) const {
+ AutoRealm call(cx, wrappedObject(wrapper));
+ if (!cx->compartment()->wrap(cx, v)) {
+ return false;
+ }
+ return Wrapper::hasInstance(cx, wrapper, v, bp);
+}
+
+const char* CrossCompartmentWrapper::className(JSContext* cx,
+ HandleObject wrapper) const {
+ AutoRealm call(cx, wrappedObject(wrapper));
+ return Wrapper::className(cx, wrapper);
+}
+
+JSString* CrossCompartmentWrapper::fun_toString(JSContext* cx,
+ HandleObject wrapper,
+ bool isToSource) const {
+ RootedString str(cx);
+ {
+ AutoRealm call(cx, wrappedObject(wrapper));
+ str = Wrapper::fun_toString(cx, wrapper, isToSource);
+ if (!str) {
+ return nullptr;
+ }
+ }
+ if (!cx->compartment()->wrap(cx, &str)) {
+ return nullptr;
+ }
+ return str;
+}
+
+RegExpShared* CrossCompartmentWrapper::regexp_toShared(
+ JSContext* cx, HandleObject wrapper) const {
+ RootedRegExpShared re(cx);
+ {
+ AutoRealm call(cx, wrappedObject(wrapper));
+ re = Wrapper::regexp_toShared(cx, wrapper);
+ if (!re) {
+ return nullptr;
+ }
+ }
+
+ // Get an equivalent RegExpShared associated with the current compartment.
+ RootedAtom source(cx, re->getSource());
+ cx->markAtom(source);
+ return cx->zone()->regExps().get(cx, source, re->getFlags());
+}
+
+bool CrossCompartmentWrapper::boxedValue_unbox(JSContext* cx,
+ HandleObject wrapper,
+ MutableHandleValue vp) const {
+ PIERCE(cx, wrapper, NOTHING, Wrapper::boxedValue_unbox(cx, wrapper, vp),
+ cx->compartment()->wrap(cx, vp));
+}
+
+const CrossCompartmentWrapper CrossCompartmentWrapper::singleton(0u);
+
+JS_FRIEND_API void js::NukeCrossCompartmentWrapper(JSContext* cx,
+ JSObject* wrapper) {
+ JS::Compartment* comp = wrapper->compartment();
+ auto ptr = comp->lookupWrapper(Wrapper::wrappedObject(wrapper));
+ if (ptr) {
+ comp->removeWrapper(ptr);
+ }
+ NukeRemovedCrossCompartmentWrapper(cx, wrapper);
+}
+
+JS_FRIEND_API void js::NukeCrossCompartmentWrapperIfExists(
+ JSContext* cx, JS::Compartment* source, JSObject* target) {
+ MOZ_ASSERT(source != target->compartment());
+ MOZ_ASSERT(!target->is<CrossCompartmentWrapperObject>());
+ auto ptr = source->lookupWrapper(target);
+ if (ptr) {
+ JSObject* wrapper = ptr->value().get();
+ NukeCrossCompartmentWrapper(cx, wrapper);
+ }
+}
+
+// Returns true iff all realms in the compartment have been nuked.
+static bool NukedAllRealms(JS::Compartment* comp) {
+ for (RealmsInCompartmentIter realm(comp); !realm.done(); realm.next()) {
+ if (!realm->nukedIncomingWrappers) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ * NukeChromeCrossCompartmentWrappersForGlobal reaches into chrome and cuts
+ * all of the cross-compartment wrappers that point to an object in the |target|
+ * realm. The snag here is that we need to avoid cutting wrappers that point to
+ * the window object on page navigation (inner window destruction) and only do
+ * that on tab close (outer window destruction). Thus the option of how to
+ * handle the global object.
+ */
+JS_FRIEND_API bool js::NukeCrossCompartmentWrappers(
+ JSContext* cx, const CompartmentFilter& sourceFilter, JS::Realm* target,
+ js::NukeReferencesToWindow nukeReferencesToWindow,
+ js::NukeReferencesFromTarget nukeReferencesFromTarget) {
+ CHECK_THREAD(cx);
+ JSRuntime* rt = cx->runtime();
+
+ // If we're nuking all wrappers into the target realm, prevent us from
+ // creating new wrappers for it in the future.
+ if (nukeReferencesFromTarget == NukeAllReferences) {
+ target->nukedIncomingWrappers = true;
+ }
+
+ for (CompartmentsIter c(rt); !c.done(); c.next()) {
+ if (!sourceFilter.match(c)) {
+ continue;
+ }
+
+ // If the realm matches both the source and target filter, we may want to
+ // cut outgoing wrappers too, if we nuked all realms in the compartment.
+ bool nukeAll =
+ (nukeReferencesFromTarget == NukeAllReferences &&
+ target->compartment() == c.get() && NukedAllRealms(c.get()));
+
+ // Iterate only the wrappers that have target compartment matched unless
+ // |nukeAll| is true. Use Maybe to avoid copying from conditionally
+ // initializing ObjectWrapperEnum.
+ mozilla::Maybe<Compartment::ObjectWrapperEnum> e;
+ if (MOZ_LIKELY(!nukeAll)) {
+ e.emplace(c, target->compartment());
+ } else {
+ e.emplace(c);
+ c.get()->nukedOutgoingWrappers = true;
+ }
+ for (; !e->empty(); e->popFront()) {
+ JSObject* key = e->front().key();
+
+ AutoWrapperRooter wobj(cx, WrapperValue(*e));
+
+ // Unwrap from the wrapped object in key instead of the wrapper, this
+ // could save us a bit of time.
+ JSObject* wrapped = UncheckedUnwrap(key);
+
+ // Don't nuke wrappers for objects in other realms in the target
+ // compartment unless nukeAll is set because in that case we want to nuke
+ // all outgoing wrappers for the current compartment.
+ if (!nukeAll && wrapped->nonCCWRealm() != target) {
+ continue;
+ }
+
+ // We never nuke ScriptSourceObjects, since they are only ever used
+ // internally by the JS engine, and are expected to remain valid
+ // throughout a script's lifetime.
+ if (MOZ_UNLIKELY(wrapped->is<ScriptSourceObject>())) {
+ continue;
+ }
+
+ // We only skip nuking window references that point to a target
+ // compartment, not the ones that belong to it.
+ if (nukeReferencesToWindow == DontNukeWindowReferences &&
+ MOZ_LIKELY(!nukeAll) && IsWindowProxy(wrapped)) {
+ continue;
+ }
+
+ // Now this is the wrapper we want to nuke.
+ e->removeFront();
+ NukeRemovedCrossCompartmentWrapper(cx, wobj);
+ }
+ }
+
+ return true;
+}
+
+JS_FRIEND_API bool js::AllowNewWrapper(JS::Compartment* target, JSObject* obj) {
+ // Disallow creating new wrappers if we nuked the object realm or target
+ // compartment. However, we always need to provide live wrappers for
+ // ScriptSourceObjects, since they're used for cross-compartment cloned
+ // scripts, and need to remain accessible even after the original realm has
+ // been nuked.
+
+ MOZ_ASSERT(obj->compartment() != target);
+
+ if (obj->is<ScriptSourceObject>()) {
+ return true;
+ }
+
+ if (target->nukedOutgoingWrappers ||
+ obj->nonCCWRealm()->nukedIncomingWrappers) {
+ return false;
+ }
+
+ return true;
+}
+
+JS_FRIEND_API bool js::NukedObjectRealm(JSObject* obj) {
+ return obj->nonCCWRealm()->nukedIncomingWrappers;
+}
+
+// Given a cross-compartment wrapper |wobj|, update it to point to
+// |newTarget|. This recomputes the wrapper with JS_WrapValue, and thus can be
+// useful even if wrapper already points to newTarget.
+// This operation crashes on failure rather than leaving the heap in an
+// inconsistent state.
+void js::RemapWrapper(JSContext* cx, JSObject* wobjArg,
+ JSObject* newTargetArg) {
+ MOZ_ASSERT(!IsInsideNursery(wobjArg));
+ MOZ_ASSERT(!IsInsideNursery(newTargetArg));
+
+ RootedObject wobj(cx, wobjArg);
+ RootedObject newTarget(cx, newTargetArg);
+ MOZ_ASSERT(wobj->is<CrossCompartmentWrapperObject>());
+ MOZ_ASSERT(!newTarget->is<CrossCompartmentWrapperObject>());
+ JSObject* origTarget = Wrapper::wrappedObject(wobj);
+ MOZ_ASSERT(origTarget);
+ JS::Compartment* wcompartment = wobj->compartment();
+ MOZ_ASSERT(wcompartment != newTarget->compartment());
+
+ AutoDisableProxyCheck adpc;
+
+ // If we're mapping to a different target (as opposed to just recomputing
+ // for the same target), we must not have an existing wrapper for the new
+ // target, otherwise this will break.
+ MOZ_ASSERT_IF(origTarget != newTarget,
+ !wcompartment->lookupWrapper(newTarget));
+
+ // The old value should still be in the cross-compartment wrapper map, and
+ // the lookup should return wobj.
+ ObjectWrapperMap::Ptr p = wcompartment->lookupWrapper(origTarget);
+ MOZ_ASSERT(*p->value().unsafeGet() == wobj);
+ wcompartment->removeWrapper(p);
+
+ // When we remove origv from the wrapper map, its wrapper, wobj, must
+ // immediately cease to be a cross-compartment wrapper. Nuke it.
+ NukeCrossCompartmentWrapper(cx, wobj);
+
+ // If the target is a dead wrapper, and we're just fixing wrappers for
+ // it, then we're done now that the CCW is a dead wrapper.
+ if (JS_IsDeadWrapper(origTarget)) {
+ MOZ_RELEASE_ASSERT(origTarget == newTarget);
+ return;
+ }
+
+ js::RemapDeadWrapper(cx, wobj, newTarget);
+}
+
+// Given a dead proxy object |wobj|, turn it into a cross-compartment wrapper
+// pointing at |newTarget|.
+// This operation crashes on failure rather than leaving the heap in an
+// inconsistent state.
+void js::RemapDeadWrapper(JSContext* cx, HandleObject wobj,
+ HandleObject newTarget) {
+ MOZ_ASSERT(IsDeadProxyObject(wobj));
+ MOZ_ASSERT(!newTarget->is<CrossCompartmentWrapperObject>());
+
+ AutoDisableProxyCheck adpc;
+
+ // wobj is not a cross-compartment wrapper, so we can use nonCCWRealm.
+ Realm* wrealm = wobj->nonCCWRealm();
+
+ // First, we wrap it in the new compartment. We try to use the existing
+ // wrapper, |wobj|, since it's been nuked anyway. The rewrap() function has
+ // the choice to reuse |wobj| or not.
+ RootedObject tobj(cx, newTarget);
+ AutoRealmUnchecked ar(cx, wrealm);
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ JS::Compartment* wcompartment = wobj->compartment();
+ if (!wcompartment->rewrap(cx, &tobj, wobj)) {
+ oomUnsafe.crash("js::RemapWrapper");
+ }
+
+ // If rewrap() reused |wobj|, it will have overwritten it and returned with
+ // |tobj == wobj|. Otherwise, |tobj| will point to a new wrapper and |wobj|
+ // will still be nuked. In the latter case, we replace |wobj| with the
+ // contents of the new wrapper in |tobj|.
+ if (tobj != wobj) {
+ // Now, because we need to maintain object identity, we do a brain
+ // transplant on the old object so that it contains the contents of the
+ // new one.
+ JSObject::swap(cx, wobj, tobj, oomUnsafe);
+ }
+
+ if (!wobj->is<WrapperObject>()) {
+ MOZ_ASSERT(js::IsDOMRemoteProxyObject(wobj));
+ return;
+ }
+
+ // Before swapping, this wrapper came out of rewrap(), which enforces the
+ // invariant that the wrapper in the map points directly to the key.
+ MOZ_ASSERT(Wrapper::wrappedObject(wobj) == newTarget);
+
+ // Update the incremental weakmap marking state.
+ wobj->zone()->afterAddDelegate(wobj);
+
+ // Update the entry in the compartment's wrapper map to point to the old
+ // wrapper, which has now been updated (via reuse or swap).
+ if (!wcompartment->putWrapper(cx, newTarget, wobj)) {
+ oomUnsafe.crash("js::RemapWrapper");
+ }
+}
+
+// Remap all cross-compartment wrappers pointing to |oldTarget| to point to
+// |newTarget|. All wrappers are recomputed.
+JS_FRIEND_API bool js::RemapAllWrappersForObject(JSContext* cx,
+ HandleObject oldTarget,
+ HandleObject newTarget) {
+ MOZ_ASSERT(!IsInsideNursery(oldTarget));
+ MOZ_ASSERT(!IsInsideNursery(newTarget));
+
+ AutoWrapperVector toTransplant(cx);
+
+ for (CompartmentsIter c(cx->runtime()); !c.done(); c.next()) {
+ if (ObjectWrapperMap::Ptr wp = c->lookupWrapper(oldTarget)) {
+ // We found a wrapper. Remember and root it.
+ if (!toTransplant.append(WrapperValue(wp))) {
+ return false;
+ }
+ }
+ }
+
+ for (const WrapperValue& v : toTransplant) {
+ RemapWrapper(cx, v, newTarget);
+ }
+
+ return true;
+}
+
+JS_FRIEND_API bool js::RecomputeWrappers(
+ JSContext* cx, const CompartmentFilter& sourceFilter,
+ const CompartmentFilter& targetFilter) {
+ bool evictedNursery = false;
+
+ AutoWrapperVector toRecompute(cx);
+ for (CompartmentsIter c(cx->runtime()); !c.done(); c.next()) {
+ // Filter by source compartment.
+ if (!sourceFilter.match(c)) {
+ continue;
+ }
+
+ if (!evictedNursery &&
+ c->hasNurseryAllocatedObjectWrapperEntries(targetFilter)) {
+ cx->runtime()->gc.evictNursery();
+ evictedNursery = true;
+ }
+
+ // Iterate over object wrappers, filtering appropriately.
+ for (Compartment::ObjectWrapperEnum e(c, targetFilter); !e.empty();
+ e.popFront()) {
+ // Add the wrapper to the list.
+ if (!toRecompute.append(WrapperValue(e))) {
+ return false;
+ }
+ }
+ }
+
+ // Recompute all the wrappers in the list.
+ for (const WrapperValue& wrapper : toRecompute) {
+ JSObject* wrapped = Wrapper::wrappedObject(wrapper);
+ RemapWrapper(cx, wrapper, wrapped);
+ }
+
+ return true;
+}
diff --git a/js/src/proxy/DOMProxy.cpp b/js/src/proxy/DOMProxy.cpp
new file mode 100644
index 0000000000..eab5390158
--- /dev/null
+++ b/js/src/proxy/DOMProxy.cpp
@@ -0,0 +1,41 @@
+/* -*- 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/. */
+
+/* DOM proxy-related functionality, including expando support. */
+
+#include "js/friend/DOMProxy.h" // JS::DOMProxyShadowsCheck
+#include "proxy/DOMProxy.h"
+
+#include "js/Proxy.h" // js::GetProxyHandler, js::IsProxy
+
+using JS::DOMProxyShadowsCheck;
+
+static const void* gDOMProxyHandlerFamily = nullptr;
+static DOMProxyShadowsCheck gDOMProxyShadowsCheck = nullptr;
+static const void* gDOMRemoteProxyHandlerFamily = nullptr;
+
+void JS::SetDOMProxyInformation(const void* domProxyHandlerFamily,
+ DOMProxyShadowsCheck domProxyShadowsCheck,
+ const void* domRemoteProxyHandlerFamily) {
+ gDOMProxyHandlerFamily = domProxyHandlerFamily;
+ gDOMProxyShadowsCheck = domProxyShadowsCheck;
+ gDOMRemoteProxyHandlerFamily = domRemoteProxyHandlerFamily;
+}
+
+const void* js::GetDOMProxyHandlerFamily() { return gDOMProxyHandlerFamily; }
+
+DOMProxyShadowsCheck js::GetDOMProxyShadowsCheck() {
+ return gDOMProxyShadowsCheck;
+}
+
+const void* js::GetDOMRemoteProxyHandlerFamily() {
+ return gDOMRemoteProxyHandlerFamily;
+}
+
+bool js::IsDOMRemoteProxyObject(JSObject* object) {
+ return js::IsProxy(object) && js::GetProxyHandler(object)->family() ==
+ js::GetDOMRemoteProxyHandlerFamily();
+}
diff --git a/js/src/proxy/DOMProxy.h b/js/src/proxy/DOMProxy.h
new file mode 100644
index 0000000000..91975658b1
--- /dev/null
+++ b/js/src/proxy/DOMProxy.h
@@ -0,0 +1,34 @@
+/* -*- 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/. */
+
+/* DOM proxy details that don't need to be exported as friend API. */
+
+#ifndef proxy_DOMProxy_h
+#define proxy_DOMProxy_h
+
+#include "js/friend/DOMProxy.h" // JS::DOMProxyShadowsCheck
+
+class JS_PUBLIC_API JSObject;
+
+namespace js {
+
+extern const void* GetDOMProxyHandlerFamily();
+
+extern JS::DOMProxyShadowsCheck GetDOMProxyShadowsCheck();
+
+inline bool DOMProxyIsShadowing(JS::DOMProxyShadowsResult result) {
+ return result == JS::DOMProxyShadowsResult::Shadows ||
+ result == JS::DOMProxyShadowsResult::ShadowsViaDirectExpando ||
+ result == JS::DOMProxyShadowsResult::ShadowsViaIndirectExpando;
+}
+
+extern const void* GetDOMRemoteProxyHandlerFamily();
+
+extern bool IsDOMRemoteProxyObject(JSObject* object);
+
+} // namespace js
+
+#endif // proxy_DOMProxy_h
diff --git a/js/src/proxy/DeadObjectProxy.cpp b/js/src/proxy/DeadObjectProxy.cpp
new file mode 100644
index 0000000000..e675baa986
--- /dev/null
+++ b/js/src/proxy/DeadObjectProxy.cpp
@@ -0,0 +1,178 @@
+/* -*- 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 "proxy/DeadObjectProxy.h"
+
+#include "jsapi.h"
+
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "vm/JSFunction.h" // XXXefaust Bug 1064662
+#include "vm/ProxyObject.h"
+
+using namespace js;
+
+const DeadObjectProxy DeadObjectProxy::singleton;
+const char DeadObjectProxy::family = 0;
+
+static void ReportDead(JSContext* cx) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
+}
+
+bool DeadObjectProxy::getOwnPropertyDescriptor(
+ JSContext* cx, HandleObject wrapper, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const {
+ ReportDead(cx);
+ return false;
+}
+
+bool DeadObjectProxy::defineProperty(JSContext* cx, HandleObject wrapper,
+ HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const {
+ ReportDead(cx);
+ return false;
+}
+
+bool DeadObjectProxy::ownPropertyKeys(JSContext* cx, HandleObject wrapper,
+ MutableHandleIdVector props) const {
+ ReportDead(cx);
+ return false;
+}
+
+bool DeadObjectProxy::delete_(JSContext* cx, HandleObject wrapper, HandleId id,
+ ObjectOpResult& result) const {
+ ReportDead(cx);
+ return false;
+}
+
+bool DeadObjectProxy::getPrototype(JSContext* cx, HandleObject proxy,
+ MutableHandleObject protop) const {
+ protop.set(nullptr);
+ return true;
+}
+
+bool DeadObjectProxy::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy,
+ bool* isOrdinary,
+ MutableHandleObject protop) const {
+ *isOrdinary = false;
+ return true;
+}
+
+bool DeadObjectProxy::preventExtensions(JSContext* cx, HandleObject proxy,
+ ObjectOpResult& result) const {
+ ReportDead(cx);
+ return false;
+}
+
+bool DeadObjectProxy::isExtensible(JSContext* cx, HandleObject proxy,
+ bool* extensible) const {
+ // This is kind of meaningless, but dead-object semantics aside,
+ // [[Extensible]] always being true is consistent with other proxy types.
+ *extensible = true;
+ return true;
+}
+
+bool DeadObjectProxy::call(JSContext* cx, HandleObject wrapper,
+ const CallArgs& args) const {
+ ReportDead(cx);
+ return false;
+}
+
+bool DeadObjectProxy::construct(JSContext* cx, HandleObject wrapper,
+ const CallArgs& args) const {
+ ReportDead(cx);
+ return false;
+}
+
+bool DeadObjectProxy::nativeCall(JSContext* cx, IsAcceptableThis test,
+ NativeImpl impl, const CallArgs& args) const {
+ ReportDead(cx);
+ return false;
+}
+
+bool DeadObjectProxy::hasInstance(JSContext* cx, HandleObject proxy,
+ MutableHandleValue v, bool* bp) const {
+ ReportDead(cx);
+ return false;
+}
+
+bool DeadObjectProxy::getBuiltinClass(JSContext* cx, HandleObject proxy,
+ ESClass* cls) const {
+ ReportDead(cx);
+ return false;
+}
+
+bool DeadObjectProxy::isArray(JSContext* cx, HandleObject obj,
+ JS::IsArrayAnswer* answer) const {
+ ReportDead(cx);
+ return false;
+}
+
+const char* DeadObjectProxy::className(JSContext* cx,
+ HandleObject wrapper) const {
+ return "DeadObject";
+}
+
+JSString* DeadObjectProxy::fun_toString(JSContext* cx, HandleObject proxy,
+ bool isToSource) const {
+ ReportDead(cx);
+ return nullptr;
+}
+
+RegExpShared* DeadObjectProxy::regexp_toShared(JSContext* cx,
+ HandleObject proxy) const {
+ ReportDead(cx);
+ return nullptr;
+}
+
+bool js::IsDeadProxyObject(JSObject* obj) {
+ return IsDerivedProxyObject(obj, &DeadObjectProxy::singleton);
+}
+
+Value js::DeadProxyTargetValue(ProxyObject* obj) {
+ // When nuking scripted proxies, isCallable and isConstructor values for
+ // the proxy needs to be preserved. So does background-finalization status.
+ int32_t flags = 0;
+ if (obj->handler()->isCallable(obj)) {
+ flags |= DeadObjectProxyIsCallable;
+ }
+ if (obj->handler()->isConstructor(obj)) {
+ flags |= DeadObjectProxyIsConstructor;
+ }
+ if (obj->handler()->finalizeInBackground(obj->private_())) {
+ flags |= DeadObjectProxyIsBackgroundFinalized;
+ }
+ return Int32Value(flags);
+}
+
+JSObject* js::NewDeadProxyObject(JSContext* cx, JSObject* origObj) {
+ MOZ_ASSERT_IF(origObj, origObj->is<ProxyObject>());
+
+ RootedValue target(cx);
+ if (origObj && origObj->is<ProxyObject>()) {
+ target = DeadProxyTargetValue(&origObj->as<ProxyObject>());
+ } else {
+ target = Int32Value(DeadObjectProxyIsBackgroundFinalized);
+ }
+
+ return NewProxyObject(cx, &DeadObjectProxy::singleton, target, nullptr,
+ ProxyOptions());
+}
+
+JSObject* js::NewDeadProxyObject(JSContext* cx, IsCallableFlag isCallable,
+ IsConstructorFlag isConstructor) {
+ int32_t flags = 0;
+ if (isCallable == IsCallableFlag::True) {
+ flags |= DeadObjectProxyIsCallable;
+ }
+ if (isConstructor == IsConstructorFlag::True) {
+ flags |= DeadObjectProxyIsConstructor;
+ }
+
+ RootedValue target(cx, Int32Value(flags));
+ return NewProxyObject(cx, &DeadObjectProxy::singleton, target, nullptr,
+ ProxyOptions());
+}
diff --git a/js/src/proxy/DeadObjectProxy.h b/js/src/proxy/DeadObjectProxy.h
new file mode 100644
index 0000000000..a1da13a846
--- /dev/null
+++ b/js/src/proxy/DeadObjectProxy.h
@@ -0,0 +1,107 @@
+/* -*- 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/. */
+
+#ifndef proxy_DeadObjectProxy_h
+#define proxy_DeadObjectProxy_h
+
+#include "js/Proxy.h"
+
+namespace js {
+
+class ProxyObject;
+
+enum DeadObjectProxyFlags {
+ DeadObjectProxyIsCallable = 1 << 0,
+ DeadObjectProxyIsConstructor = 1 << 1,
+ DeadObjectProxyIsBackgroundFinalized = 1 << 2
+};
+
+class DeadObjectProxy : public BaseProxyHandler {
+ public:
+ explicit constexpr DeadObjectProxy() : BaseProxyHandler(&family) {}
+
+ /* Standard internal methods. */
+ virtual bool getOwnPropertyDescriptor(
+ JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
+ JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
+ virtual bool defineProperty(JSContext* cx, JS::HandleObject wrapper,
+ JS::HandleId id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult& result) const override;
+ virtual bool ownPropertyKeys(JSContext* cx, JS::HandleObject wrapper,
+ JS::MutableHandleIdVector props) const override;
+ virtual bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
+ JS::ObjectOpResult& result) const override;
+ virtual bool getPrototype(JSContext* cx, JS::HandleObject proxy,
+ JS::MutableHandleObject protop) const override;
+ virtual bool getPrototypeIfOrdinary(
+ JSContext* cx, JS::HandleObject proxy, bool* isOrdinary,
+ JS::MutableHandleObject protop) const override;
+ virtual bool preventExtensions(JSContext* cx, JS::HandleObject proxy,
+ JS::ObjectOpResult& result) const override;
+ virtual bool isExtensible(JSContext* cx, JS::HandleObject proxy,
+ bool* extensible) const override;
+ virtual bool call(JSContext* cx, JS::HandleObject proxy,
+ const JS::CallArgs& args) const override;
+ virtual bool construct(JSContext* cx, JS::HandleObject proxy,
+ const JS::CallArgs& args) const override;
+
+ /* SpiderMonkey extensions. */
+ // BaseProxyHandler::enumerate will throw by calling ownKeys.
+ virtual bool nativeCall(JSContext* cx, JS::IsAcceptableThis test,
+ JS::NativeImpl impl,
+ const JS::CallArgs& args) const override;
+ virtual bool hasInstance(JSContext* cx, JS::HandleObject proxy,
+ JS::MutableHandleValue v, bool* bp) const override;
+ virtual bool getBuiltinClass(JSContext* cx, JS::HandleObject proxy,
+ ESClass* cls) const override;
+ virtual bool isArray(JSContext* cx, JS::HandleObject proxy,
+ JS::IsArrayAnswer* answer) const override;
+ virtual const char* className(JSContext* cx,
+ JS::HandleObject proxy) const override;
+ virtual JSString* fun_toString(JSContext* cx, JS::HandleObject proxy,
+ bool isToSource) const override;
+ virtual RegExpShared* regexp_toShared(JSContext* cx,
+ JS::HandleObject proxy) const override;
+
+ // Use the regular paths for private values
+ virtual bool useProxyExpandoObjectForPrivateFields() const override {
+ return false;
+ }
+
+ virtual bool isCallable(JSObject* obj) const override {
+ return flags(obj) & DeadObjectProxyIsCallable;
+ }
+ virtual bool isConstructor(JSObject* obj) const override {
+ return flags(obj) & DeadObjectProxyIsConstructor;
+ }
+
+ virtual bool finalizeInBackground(const JS::Value& priv) const override {
+ return priv.toInt32() & DeadObjectProxyIsBackgroundFinalized;
+ }
+
+ static const DeadObjectProxy singleton;
+ static const char family;
+
+ private:
+ static int32_t flags(JSObject* obj) { return GetProxyPrivate(obj).toInt32(); }
+};
+
+bool IsDeadProxyObject(JSObject* obj);
+
+JS::Value DeadProxyTargetValue(ProxyObject* obj);
+
+JSObject* NewDeadProxyObject(JSContext* cx, JSObject* origObj = nullptr);
+
+enum class IsCallableFlag : bool { False, True };
+enum class IsConstructorFlag : bool { False, True };
+
+JSObject* NewDeadProxyObject(JSContext* cx, IsCallableFlag isCallable,
+ IsConstructorFlag isConstructor);
+
+} /* namespace js */
+
+#endif /* proxy_DeadObjectProxy_h */
diff --git a/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp b/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp
new file mode 100644
index 0000000000..b9dee324f6
--- /dev/null
+++ b/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp
@@ -0,0 +1,159 @@
+/* -*- 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/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/Wrapper.h"
+
+#include "vm/JSObject-inl.h"
+
+using namespace js;
+
+bool OpaqueCrossCompartmentWrapper::getOwnPropertyDescriptor(
+ JSContext* cx, HandleObject wrapper, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const {
+ desc.object().set(nullptr);
+ return true;
+}
+
+bool OpaqueCrossCompartmentWrapper::defineProperty(
+ JSContext* cx, HandleObject wrapper, HandleId id,
+ Handle<PropertyDescriptor> desc, ObjectOpResult& result) const {
+ return result.succeed();
+}
+
+bool OpaqueCrossCompartmentWrapper::ownPropertyKeys(
+ JSContext* cx, HandleObject wrapper, MutableHandleIdVector props) const {
+ return true;
+}
+
+bool OpaqueCrossCompartmentWrapper::delete_(JSContext* cx, HandleObject wrapper,
+ HandleId id,
+ ObjectOpResult& result) const {
+ return result.succeed();
+}
+
+bool OpaqueCrossCompartmentWrapper::enumerate(
+ JSContext* cx, HandleObject proxy, MutableHandleIdVector props) const {
+ return BaseProxyHandler::enumerate(cx, proxy, props);
+}
+
+bool OpaqueCrossCompartmentWrapper::getPrototype(
+ JSContext* cx, HandleObject proxy, MutableHandleObject protop) const {
+ protop.set(nullptr);
+ return true;
+}
+
+bool OpaqueCrossCompartmentWrapper::setPrototype(JSContext* cx,
+ HandleObject proxy,
+ HandleObject proto,
+ ObjectOpResult& result) const {
+ return result.succeed();
+}
+
+bool OpaqueCrossCompartmentWrapper::getPrototypeIfOrdinary(
+ JSContext* cx, HandleObject proxy, bool* isOrdinary,
+ MutableHandleObject protop) const {
+ *isOrdinary = false;
+ return true;
+}
+
+bool OpaqueCrossCompartmentWrapper::setImmutablePrototype(
+ JSContext* cx, HandleObject proxy, bool* succeeded) const {
+ *succeeded = false;
+ return true;
+}
+
+bool OpaqueCrossCompartmentWrapper::preventExtensions(
+ JSContext* cx, HandleObject wrapper, ObjectOpResult& result) const {
+ return result.failCantPreventExtensions();
+}
+
+bool OpaqueCrossCompartmentWrapper::isExtensible(JSContext* cx,
+ HandleObject wrapper,
+ bool* extensible) const {
+ *extensible = true;
+ return true;
+}
+
+bool OpaqueCrossCompartmentWrapper::has(JSContext* cx, HandleObject wrapper,
+ HandleId id, bool* bp) const {
+ return BaseProxyHandler::has(cx, wrapper, id, bp);
+}
+
+bool OpaqueCrossCompartmentWrapper::get(JSContext* cx, HandleObject wrapper,
+ HandleValue receiver, HandleId id,
+ MutableHandleValue vp) const {
+ return BaseProxyHandler::get(cx, wrapper, receiver, id, vp);
+}
+
+bool OpaqueCrossCompartmentWrapper::set(JSContext* cx, HandleObject wrapper,
+ HandleId id, HandleValue v,
+ HandleValue receiver,
+ ObjectOpResult& result) const {
+ return BaseProxyHandler::set(cx, wrapper, id, v, receiver, result);
+}
+
+bool OpaqueCrossCompartmentWrapper::call(JSContext* cx, HandleObject wrapper,
+ const CallArgs& args) const {
+ RootedValue v(cx, ObjectValue(*wrapper));
+ ReportIsNotFunction(cx, v);
+ return false;
+}
+
+bool OpaqueCrossCompartmentWrapper::construct(JSContext* cx,
+ HandleObject wrapper,
+ const CallArgs& args) const {
+ RootedValue v(cx, ObjectValue(*wrapper));
+ ReportIsNotFunction(cx, v);
+ return false;
+}
+
+bool OpaqueCrossCompartmentWrapper::hasOwn(JSContext* cx, HandleObject wrapper,
+ HandleId id, bool* bp) const {
+ return BaseProxyHandler::hasOwn(cx, wrapper, id, bp);
+}
+
+bool OpaqueCrossCompartmentWrapper::getOwnEnumerablePropertyKeys(
+ JSContext* cx, HandleObject wrapper, MutableHandleIdVector props) const {
+ return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, wrapper, props);
+}
+
+bool OpaqueCrossCompartmentWrapper::getBuiltinClass(JSContext* cx,
+ HandleObject wrapper,
+ ESClass* cls) const {
+ *cls = ESClass::Other;
+ return true;
+}
+
+bool OpaqueCrossCompartmentWrapper::isArray(JSContext* cx, HandleObject obj,
+ JS::IsArrayAnswer* answer) const {
+ *answer = JS::IsArrayAnswer::NotArray;
+ return true;
+}
+
+bool OpaqueCrossCompartmentWrapper::hasInstance(JSContext* cx,
+ HandleObject wrapper,
+ MutableHandleValue v,
+ bool* bp) const {
+ *bp = false;
+ return true;
+}
+
+const char* OpaqueCrossCompartmentWrapper::className(JSContext* cx,
+ HandleObject proxy) const {
+ return "Opaque";
+}
+
+JSString* OpaqueCrossCompartmentWrapper::fun_toString(JSContext* cx,
+ HandleObject proxy,
+ bool isToSource) const {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_INCOMPATIBLE_PROTO, js_Function_str,
+ js_toString_str, "object");
+ return nullptr;
+}
+
+const OpaqueCrossCompartmentWrapper OpaqueCrossCompartmentWrapper::singleton;
diff --git a/js/src/proxy/Proxy.cpp b/js/src/proxy/Proxy.cpp
new file mode 100644
index 0000000000..d26bbed2e2
--- /dev/null
+++ b/js/src/proxy/Proxy.cpp
@@ -0,0 +1,974 @@
+/* -*- 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 <string.h>
+
+#include "jsapi.h"
+
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "js/friend/StackLimits.h" // js::CheckRecursionLimit, 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/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.
+ MOZ_ASSERT(expando);
+
+ Rooted<PropertyDescriptor> ownDesc(cx);
+ if (!GetOwnPropertyDescriptor(cx, expando, id, &ownDesc)) {
+ return false;
+ }
+ ownDesc.assertCompleteIfFound();
+
+ MOZ_ASSERT(ownDesc.object());
+
+ RootedValue expandoValue(cx, proxy->as<ProxyObject>().expando());
+ return SetPropertyIgnoringNamedGetter(cx, expando, id, v, expandoValue,
+ ownDesc, result);
+}
+
+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.
+ MOZ_ASSERT(expando);
+
+ // 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<PropertyDescriptor> desc(cx);
+ if (!GetOwnPropertyDescriptor(cx, expando, id, &desc)) {
+ return false;
+ }
+
+ // If the private name has a getter, delegate to that.
+ if (desc.hasGetterObject()) {
+ RootedValue getter(cx, JS::ObjectValue(*desc.getterObject().get()));
+ return js::CallGetter(cx, receiver, getter, vp);
+ }
+
+ // We must have the object, same reasoning as the expando.
+ MOZ_ASSERT(desc.object());
+ 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 = NewObjectWithGivenProto<PlainObject>(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 (JSID_IS_VOID(id)) {
+ 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_FRIEND_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<PropertyDescriptor> desc) {
+ if (!CheckRecursionLimit(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ desc.object().set(
+ nullptr); // 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();
+ }
+
+ // Unless we implment ProxyGetOwnPropertyDescriptorFromExpando,
+ // this would be incorrect.
+ MOZ_ASSERT_IF(handler->useProxyExpandoObjectForPrivateFields(),
+ !id.isPrivateName());
+
+ return handler->getOwnPropertyDescriptor(cx, proxy, id, desc);
+}
+
+bool Proxy::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) {
+ if (!CheckRecursionLimit(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ 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) {
+ if (!CheckRecursionLimit(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
+ 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) {
+ if (!CheckRecursionLimit(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_IF(JSID_IS_SYMBOL(id), !JSID_TO_SYMBOL(id)->isPrivateName());
+
+ return proxy->as<ProxyObject>().handler()->delete_(cx, proxy, id, result);
+}
+
+JS_FRIEND_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());
+ if (!CheckRecursionLimit(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());
+ if (!CheckRecursionLimit(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) {
+ if (!CheckRecursionLimit(cx)) {
+ return false;
+ }
+ return proxy->as<ProxyObject>().handler()->getPrototypeIfOrdinary(
+ cx, proxy, isOrdinary, proto);
+}
+
+/* static */
+bool Proxy::setImmutablePrototype(JSContext* cx, HandleObject proxy,
+ bool* succeeded) {
+ if (!CheckRecursionLimit(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) {
+ if (!CheckRecursionLimit(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) {
+ if (!CheckRecursionLimit(cx)) {
+ return false;
+ }
+ return proxy->as<ProxyObject>().handler()->isExtensible(cx, proxy,
+ extensible);
+}
+
+bool Proxy::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) {
+ if (!CheckRecursionLimit(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_IF(JSID_IS_SYMBOL(id), !JSID_TO_SYMBOL(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) {
+ if (!CheckRecursionLimit(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 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()));
+
+ if (!CheckRecursionLimit(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ 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()));
+
+ if (!CheckRecursionLimit(cx)) {
+ return false;
+ }
+
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ 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) {
+ if (!CheckRecursionLimit(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
+ BaseProxyHandler::ENUMERATE, true);
+ if (!policy.allowed()) {
+ return policy.returnValue();
+ }
+ return handler->getOwnEnumerablePropertyKeys(cx, proxy, props);
+}
+
+bool Proxy::enumerate(JSContext* cx, HandleObject proxy,
+ MutableHandleIdVector props) {
+ if (!CheckRecursionLimit(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, JSID_VOIDHANDLE,
+ 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) {
+ if (!CheckRecursionLimit(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, JSID_VOIDHANDLE,
+ 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) {
+ if (!CheckRecursionLimit(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, JSID_VOIDHANDLE,
+ 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) {
+ if (!CheckRecursionLimit(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::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
+ bool* bp) {
+ if (!CheckRecursionLimit(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, JSID_VOIDHANDLE,
+ BaseProxyHandler::GET, true);
+ if (!policy.allowed()) {
+ return policy.returnValue();
+ }
+ return proxy->as<ProxyObject>().handler()->hasInstance(cx, proxy, v, bp);
+}
+
+bool Proxy::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) {
+ if (!CheckRecursionLimit(cx)) {
+ return false;
+ }
+ return proxy->as<ProxyObject>().handler()->getBuiltinClass(cx, proxy, cls);
+}
+
+bool Proxy::isArray(JSContext* cx, HandleObject proxy,
+ JS::IsArrayAnswer* answer) {
+ if (!CheckRecursionLimit(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.
+ int stackDummy;
+ if (!JS_CHECK_STACK_SIZE(GetNativeStackLimit(cx), &stackDummy)) {
+ return "too much recursion";
+ }
+
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
+ 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) {
+ if (!CheckRecursionLimit(cx)) {
+ return nullptr;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
+ 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) {
+ if (!CheckRecursionLimit(cx)) {
+ return nullptr;
+ }
+ return proxy->as<ProxyObject>().handler()->regexp_toShared(cx, proxy);
+}
+
+bool Proxy::boxedValue_unbox(JSContext* cx, HandleObject proxy,
+ MutableHandleValue vp) {
+ if (!CheckRecursionLimit(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) {
+ if (!CheckRecursionLimit(cx)) {
+ return false;
+ }
+ const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
+ AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
+ 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,
+ MutableHandle<JS::PropertyResult> propp) {
+ bool found;
+ if (!Proxy::has(cx, obj, id, &found)) {
+ return false;
+ }
+
+ if (found) {
+ propp.setNonNativeProperty();
+ 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>();
+
+ TraceEdge(trc, proxy->shapePtr(), "ProxyObject_shape");
+ TraceNullableEdge(trc, proxy->slotOfExpando(), "expando");
+
+#ifdef DEBUG
+ if (TlsContext.get()->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(JSFreeOp* fop, JSObject* obj) {
+ // Suppress a bogus warning about finalize().
+ JS::AutoSuppressGCAnalysis nogc;
+
+ MOZ_ASSERT(obj->is<ProxyObject>());
+ obj->as<ProxyObject>().handler()->finalize(fop, obj);
+
+ if (!obj->as<ProxyObject>().usingInlineValueArray()) {
+ // Bug 1560019: This allocation is not tracked, but is only present when
+ // objects are swapped which is assumed to be relatively rare.
+ fop->freeUntracked(js::detail::GetProxyDataLayout(obj)->values());
+ }
+}
+
+size_t js::proxy_ObjectMoved(JSObject* obj, JSObject* old) {
+ ProxyObject& proxy = obj->as<ProxyObject>();
+
+ if (IsInsideNursery(old)) {
+ // Objects in the nursery are never swapped so the proxy must have an
+ // inline ProxyValueArray.
+ MOZ_ASSERT(old->as<ProxyObject>().usingInlineValueArray());
+ proxy.setInlineValueArray();
+ }
+
+ return proxy.handler()->objectMoved(obj, old);
+}
+
+const JSClassOps js::ProxyClassOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ proxy_Finalize, // finalize
+ nullptr, // call
+ Proxy::hasInstance, // hasInstance
+ 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_FRIEND_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(!IsInsideNursery(this));
+ 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());
+ }
+}
diff --git a/js/src/proxy/Proxy.h b/js/src/proxy/Proxy.h
new file mode 100644
index 0000000000..d75b9fb336
--- /dev/null
+++ b/js/src/proxy/Proxy.h
@@ -0,0 +1,114 @@
+/* -*- 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/. */
+
+#ifndef proxy_Proxy_h
+#define proxy_Proxy_h
+
+#include "NamespaceImports.h"
+
+#include "js/Array.h" // JS::IsArrayAnswer
+#include "js/Class.h"
+
+namespace js {
+
+/*
+ * Dispatch point for handlers that executes the appropriate C++ or scripted
+ * traps.
+ *
+ * Important: All proxy methods need either (a) an AutoEnterPolicy in their
+ * Proxy::foo entry point below or (b) an override in SecurityWrapper. See bug
+ * 945826 comment 0.
+ */
+class Proxy {
+ public:
+ /* Standard internal methods. */
+ static bool getOwnPropertyDescriptor(
+ JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<JS::PropertyDescriptor> desc);
+ static bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ Handle<JS::PropertyDescriptor> desc,
+ ObjectOpResult& result);
+ static bool ownPropertyKeys(JSContext* cx, HandleObject proxy,
+ MutableHandleIdVector props);
+ static bool delete_(JSContext* cx, HandleObject proxy, HandleId id,
+ ObjectOpResult& result);
+ static bool enumerate(JSContext* cx, HandleObject proxy,
+ MutableHandleIdVector props);
+ static bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible);
+ static bool preventExtensions(JSContext* cx, HandleObject proxy,
+ ObjectOpResult& result);
+ static bool getPrototype(JSContext* cx, HandleObject proxy,
+ MutableHandleObject protop);
+ static bool setPrototype(JSContext* cx, HandleObject proxy,
+ HandleObject proto, ObjectOpResult& result);
+ static bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy,
+ bool* isOrdinary,
+ MutableHandleObject protop);
+ static bool setImmutablePrototype(JSContext* cx, HandleObject proxy,
+ bool* succeeded);
+ static bool has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp);
+ static bool get(JSContext* cx, HandleObject proxy, HandleValue receiver,
+ HandleId id, MutableHandleValue vp);
+ static bool getInternal(JSContext* cx, HandleObject proxy,
+ HandleValue receiver, HandleId id,
+ MutableHandleValue vp);
+ static bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result);
+ static bool setInternal(JSContext* cx, HandleObject proxy, HandleId id,
+ HandleValue v, HandleValue receiver,
+ ObjectOpResult& result);
+ static bool call(JSContext* cx, HandleObject proxy, const CallArgs& args);
+ static bool construct(JSContext* cx, HandleObject proxy,
+ const CallArgs& args);
+
+ /* SpiderMonkey extensions. */
+ static bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp);
+ static bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
+ MutableHandleIdVector props);
+ static bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
+ const CallArgs& args);
+ static bool hasInstance(JSContext* cx, HandleObject proxy,
+ MutableHandleValue v, bool* bp);
+ static bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls);
+ static bool isArray(JSContext* cx, HandleObject proxy,
+ JS::IsArrayAnswer* answer);
+ static const char* className(JSContext* cx, HandleObject proxy);
+ static JSString* fun_toString(JSContext* cx, HandleObject proxy,
+ bool isToSource);
+ static RegExpShared* regexp_toShared(JSContext* cx, HandleObject proxy);
+ static bool boxedValue_unbox(JSContext* cx, HandleObject proxy,
+ MutableHandleValue vp);
+
+ static bool getElements(JSContext* cx, HandleObject obj, uint32_t begin,
+ uint32_t end, ElementAdder* adder);
+
+ static void trace(JSTracer* trc, JSObject* obj);
+};
+
+size_t proxy_ObjectMoved(JSObject* obj, JSObject* old);
+
+// These functions are used by JIT code
+
+bool ProxyHas(JSContext* cx, HandleObject proxy, HandleValue idVal,
+ bool* result);
+
+bool ProxyHasOwn(JSContext* cx, HandleObject proxy, HandleValue idVal,
+ bool* result);
+
+bool ProxyGetProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandleValue vp);
+
+bool ProxyGetPropertyByValue(JSContext* cx, HandleObject proxy,
+ HandleValue idVal, MutableHandleValue vp);
+
+bool ProxySetProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ HandleValue val, bool strict);
+
+bool ProxySetPropertyByValue(JSContext* cx, HandleObject proxy,
+ HandleValue idVal, HandleValue val, bool strict);
+} /* namespace js */
+
+#endif /* proxy_Proxy_h */
diff --git a/js/src/proxy/ScriptedProxyHandler.cpp b/js/src/proxy/ScriptedProxyHandler.cpp
new file mode 100644
index 0000000000..9d2d17f8a6
--- /dev/null
+++ b/js/src/proxy/ScriptedProxyHandler.cpp
@@ -0,0 +1,1580 @@
+/* -*- 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 "proxy/ScriptedProxyHandler.h"
+
+#include "jsapi.h"
+
+#include "js/CharacterEncoding.h"
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/PropertyDescriptor.h" // JS::FromPropertyDescriptor
+#include "vm/EqualityOperations.h" // js::SameValue
+#include "vm/JSFunction.h"
+#include "vm/JSObject.h"
+#include "vm/PlainObject.h" // js::PlainObject
+
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+using JS::IsArrayAnswer;
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 9.1.6.2 IsCompatiblePropertyDescriptor. BUT that method just calls
+// 9.1.6.3 ValidateAndApplyPropertyDescriptor with two additional constant
+// arguments. Therefore step numbering is from the latter method, and
+// resulting dead code has been removed.
+
+// If an exception should be thrown, we will set errorDetails.
+static bool IsCompatiblePropertyDescriptor(JSContext* cx, bool extensible,
+ Handle<PropertyDescriptor> desc,
+ Handle<PropertyDescriptor> current,
+ const char** errorDetails) {
+ // precondition: we won't set details if checks pass, so it must be null
+ // here.
+ MOZ_ASSERT(*errorDetails == nullptr);
+
+ // Step 2.
+ if (!current.object()) {
+ // Step 2a-b,e. As |O| is always undefined, steps 2c-d fall away.
+ if (!extensible) {
+ static const char DETAILS_NOT_EXTENSIBLE[] =
+ "proxy can't report an extensible object as non-extensible";
+ *errorDetails = DETAILS_NOT_EXTENSIBLE;
+ }
+ return true;
+ }
+
+ // Step 3.
+ if (!desc.hasValue() && !desc.hasWritable() && !desc.hasGetterObject() &&
+ !desc.hasSetterObject() && !desc.hasEnumerable() &&
+ !desc.hasConfigurable()) {
+ return true;
+ }
+
+ // Step 4.
+ if ((!desc.hasWritable() ||
+ (current.hasWritable() && desc.writable() == current.writable())) &&
+ (!desc.hasGetterObject() || desc.getter() == current.getter()) &&
+ (!desc.hasSetterObject() || desc.setter() == current.setter()) &&
+ (!desc.hasEnumerable() || desc.enumerable() == current.enumerable()) &&
+ (!desc.hasConfigurable() ||
+ desc.configurable() == current.configurable())) {
+ if (!desc.hasValue()) {
+ return true;
+ }
+
+ bool same = false;
+ if (!SameValue(cx, desc.value(), current.value(), &same)) {
+ return false;
+ }
+
+ if (same) {
+ return true;
+ }
+ }
+
+ // Step 5.
+ if (!current.configurable()) {
+ // Step 5a.
+ if (desc.hasConfigurable() && desc.configurable()) {
+ static const char DETAILS_CANT_REPORT_NC_AS_C[] =
+ "proxy can't report an existing non-configurable property as "
+ "configurable";
+ *errorDetails = DETAILS_CANT_REPORT_NC_AS_C;
+ return true;
+ }
+
+ // Step 5b.
+ if (desc.hasEnumerable() && desc.enumerable() != current.enumerable()) {
+ static const char DETAILS_ENUM_DIFFERENT[] =
+ "proxy can't report a different 'enumerable' from target when target "
+ "is not configurable";
+ *errorDetails = DETAILS_ENUM_DIFFERENT;
+ return true;
+ }
+ }
+
+ // Step 6.
+ if (desc.isGenericDescriptor()) {
+ return true;
+ }
+
+ // Step 7.
+ if (current.isDataDescriptor() != desc.isDataDescriptor()) {
+ // Steps 7a, 11. As |O| is always undefined, steps 2b-c fall away.
+ if (!current.configurable()) {
+ static const char DETAILS_CURRENT_NC_DIFF_TYPE[] =
+ "proxy can't report a different descriptor type when target is not "
+ "configurable";
+ *errorDetails = DETAILS_CURRENT_NC_DIFF_TYPE;
+ }
+ return true;
+ }
+
+ // Step 8.
+ if (current.isDataDescriptor()) {
+ MOZ_ASSERT(desc.isDataDescriptor()); // by step 7
+ if (!current.configurable() && !current.writable()) {
+ if (desc.hasWritable() && desc.writable()) {
+ static const char DETAILS_CANT_REPORT_NW_AS_W[] =
+ "proxy can't report a non-configurable, non-writable property as "
+ "writable";
+ *errorDetails = DETAILS_CANT_REPORT_NW_AS_W;
+ return true;
+ }
+
+ if (desc.hasValue()) {
+ bool same;
+ if (!SameValue(cx, desc.value(), current.value(), &same)) {
+ return false;
+ }
+ if (!same) {
+ static const char DETAILS_DIFFERENT_VALUE[] =
+ "proxy must report the same value for the non-writable, "
+ "non-configurable property";
+ *errorDetails = DETAILS_DIFFERENT_VALUE;
+ return true;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ // Step 9.
+ MOZ_ASSERT(current.isAccessorDescriptor()); // by step 8
+ MOZ_ASSERT(desc.isAccessorDescriptor()); // by step 7
+
+ if (current.configurable()) {
+ return true;
+ }
+ if (desc.hasSetterObject() && (desc.setter() != current.setter())) {
+ static const char DETAILS_SETTERS_DIFFERENT[] =
+ "proxy can't report different setters for a currently non-configurable "
+ "property";
+ *errorDetails = DETAILS_SETTERS_DIFFERENT;
+ } else if (desc.hasGetterObject() && (desc.getter() != current.getter())) {
+ static const char DETAILS_GETTERS_DIFFERENT[] =
+ "proxy can't report different getters for a currently non-configurable "
+ "property";
+ *errorDetails = DETAILS_GETTERS_DIFFERENT;
+ }
+ return true;
+}
+
+// Get the [[ProxyHandler]] of a scripted proxy.
+/* static */
+JSObject* ScriptedProxyHandler::handlerObject(const JSObject* proxy) {
+ MOZ_ASSERT(proxy->as<ProxyObject>().handler() ==
+ &ScriptedProxyHandler::singleton);
+ return proxy->as<ProxyObject>()
+ .reservedSlot(ScriptedProxyHandler::HANDLER_EXTRA)
+ .toObjectOrNull();
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 7.3.9 GetMethod, reimplemented for proxy handler trap-getting to produce
+// better error messages.
+static bool GetProxyTrap(JSContext* cx, HandleObject handler,
+ HandlePropertyName name, MutableHandleValue func) {
+ // Steps 2, 5.
+ if (!GetProperty(cx, handler, handler, name, func)) {
+ return false;
+ }
+
+ // Step 3.
+ if (func.isUndefined()) {
+ return true;
+ }
+
+ if (func.isNull()) {
+ func.setUndefined();
+ return true;
+ }
+
+ // Step 4.
+ if (!IsCallable(func)) {
+ UniqueChars bytes = EncodeAscii(cx, name);
+ if (!bytes) {
+ return false;
+ }
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_TRAP,
+ bytes.get());
+ return false;
+ }
+
+ return true;
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 9.5.1 Proxy.[[GetPrototypeOf]].
+bool ScriptedProxyHandler::getPrototype(JSContext* cx, HandleObject proxy,
+ MutableHandleObject protop) const {
+ // Steps 1-3.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 4.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 5.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().getPrototypeOf, &trap)) {
+ return false;
+ }
+
+ // Step 6.
+ if (trap.isUndefined()) {
+ return GetPrototype(cx, target, protop);
+ }
+
+ // Step 7.
+ RootedValue handlerProto(cx);
+ {
+ FixedInvokeArgs<1> args(cx);
+
+ args[0].setObject(*target);
+
+ handlerProto.setObject(*handler);
+
+ if (!js::Call(cx, trap, handlerProto, args, &handlerProto)) {
+ return false;
+ }
+ }
+
+ // Step 8.
+ if (!handlerProto.isObjectOrNull()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_BAD_GETPROTOTYPEOF_TRAP_RETURN);
+ return false;
+ }
+
+ // Step 9.
+ bool extensibleTarget;
+ if (!IsExtensible(cx, target, &extensibleTarget)) {
+ return false;
+ }
+
+ // Step 10.
+ if (extensibleTarget) {
+ protop.set(handlerProto.toObjectOrNull());
+ return true;
+ }
+
+ // Step 11.
+ RootedObject targetProto(cx);
+ if (!GetPrototype(cx, target, &targetProto)) {
+ return false;
+ }
+
+ // Step 12.
+ if (handlerProto.toObjectOrNull() != targetProto) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_INCONSISTENT_GETPROTOTYPEOF_TRAP);
+ return false;
+ }
+
+ // Step 13.
+ protop.set(handlerProto.toObjectOrNull());
+ return true;
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 9.5.2 Proxy.[[SetPrototypeOf]].
+bool ScriptedProxyHandler::setPrototype(JSContext* cx, HandleObject proxy,
+ HandleObject proto,
+ ObjectOpResult& result) const {
+ // Steps 1-4.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 5.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 6.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().setPrototypeOf, &trap)) {
+ return false;
+ }
+
+ // Step 7.
+ if (trap.isUndefined()) {
+ return SetPrototype(cx, target, proto, result);
+ }
+
+ // Step 8.
+ bool booleanTrapResult;
+ {
+ FixedInvokeArgs<2> args(cx);
+
+ args[0].setObject(*target);
+ args[1].setObjectOrNull(proto);
+
+ RootedValue hval(cx, ObjectValue(*handler));
+ if (!js::Call(cx, trap, hval, args, &hval)) {
+ return false;
+ }
+
+ booleanTrapResult = ToBoolean(hval);
+ }
+
+ // Step 9.
+ if (!booleanTrapResult) {
+ return result.fail(JSMSG_PROXY_SETPROTOTYPEOF_RETURNED_FALSE);
+ }
+
+ // Step 10.
+ bool extensibleTarget;
+ if (!IsExtensible(cx, target, &extensibleTarget)) {
+ return false;
+ }
+
+ // Step 11.
+ if (extensibleTarget) {
+ return result.succeed();
+ }
+
+ // Step 12.
+ RootedObject targetProto(cx);
+ if (!GetPrototype(cx, target, &targetProto)) {
+ return false;
+ }
+
+ // Step 13.
+ if (proto != targetProto) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_INCONSISTENT_SETPROTOTYPEOF_TRAP);
+ return false;
+ }
+
+ // Step 14.
+ return result.succeed();
+}
+
+bool ScriptedProxyHandler::getPrototypeIfOrdinary(
+ JSContext* cx, HandleObject proxy, bool* isOrdinary,
+ MutableHandleObject protop) const {
+ *isOrdinary = false;
+ return true;
+}
+
+// Not yet part of ES6, but hopefully to be standards-tracked -- and needed to
+// handle revoked proxies in any event.
+bool ScriptedProxyHandler::setImmutablePrototype(JSContext* cx,
+ HandleObject proxy,
+ bool* succeeded) const {
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ if (!target) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ return SetImmutablePrototype(cx, target, succeeded);
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 9.5.4 Proxy.[[PreventExtensions]]()
+bool ScriptedProxyHandler::preventExtensions(JSContext* cx, HandleObject proxy,
+ ObjectOpResult& result) const {
+ // Steps 1-3.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 4.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 5.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().preventExtensions, &trap)) {
+ return false;
+ }
+
+ // Step 6.
+ if (trap.isUndefined()) {
+ return PreventExtensions(cx, target, result);
+ }
+
+ // Step 7.
+ bool booleanTrapResult;
+ {
+ RootedValue arg(cx, ObjectValue(*target));
+ RootedValue trapResult(cx);
+ if (!Call(cx, trap, handler, arg, &trapResult)) {
+ return false;
+ }
+
+ booleanTrapResult = ToBoolean(trapResult);
+ }
+
+ // Step 8.
+ if (booleanTrapResult) {
+ // Step 8a.
+ bool targetIsExtensible;
+ if (!IsExtensible(cx, target, &targetIsExtensible)) {
+ return false;
+ }
+
+ if (targetIsExtensible) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_CANT_REPORT_AS_NON_EXTENSIBLE);
+ return false;
+ }
+
+ // Step 9.
+ return result.succeed();
+ }
+
+ // Also step 9.
+ return result.fail(JSMSG_PROXY_PREVENTEXTENSIONS_RETURNED_FALSE);
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 9.5.3 Proxy.[[IsExtensible]]()
+bool ScriptedProxyHandler::isExtensible(JSContext* cx, HandleObject proxy,
+ bool* extensible) const {
+ // Steps 1-3.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 4.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 5.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().isExtensible, &trap)) {
+ return false;
+ }
+
+ // Step 6.
+ if (trap.isUndefined()) {
+ return IsExtensible(cx, target, extensible);
+ }
+
+ // Step 7.
+ bool booleanTrapResult;
+ {
+ RootedValue arg(cx, ObjectValue(*target));
+ RootedValue trapResult(cx);
+ if (!Call(cx, trap, handler, arg, &trapResult)) {
+ return false;
+ }
+
+ booleanTrapResult = ToBoolean(trapResult);
+ }
+
+ // Steps 8.
+ bool targetResult;
+ if (!IsExtensible(cx, target, &targetResult)) {
+ return false;
+ }
+
+ // Step 9.
+ if (targetResult != booleanTrapResult) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_EXTENSIBILITY);
+ return false;
+ }
+
+ // Step 10.
+ *extensible = booleanTrapResult;
+ return true;
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 9.5.5 Proxy.[[GetOwnProperty]](P)
+bool ScriptedProxyHandler::getOwnPropertyDescriptor(
+ JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const {
+ // Steps 2-4.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 5.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 6.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().getOwnPropertyDescriptor, &trap)) {
+ return false;
+ }
+
+ // Step 7.
+ if (trap.isUndefined()) {
+ return GetOwnPropertyDescriptor(cx, target, id, desc);
+ }
+
+ // Step 8.
+ RootedValue propKey(cx);
+ if (!IdToStringOrSymbol(cx, id, &propKey)) {
+ return false;
+ }
+
+ RootedValue trapResult(cx);
+ RootedValue targetVal(cx, ObjectValue(*target));
+ if (!Call(cx, trap, handler, targetVal, propKey, &trapResult)) {
+ return false;
+ }
+
+ // Step 9.
+ if (!trapResult.isUndefined() && !trapResult.isObject()) {
+ return js::Throw(cx, id, JSMSG_PROXY_GETOWN_OBJORUNDEF);
+ }
+
+ // Step 10.
+ Rooted<PropertyDescriptor> targetDesc(cx);
+ if (!GetOwnPropertyDescriptor(cx, target, id, &targetDesc)) {
+ return false;
+ }
+
+ // Step 11.
+ if (trapResult.isUndefined()) {
+ // Step 11a.
+ if (!targetDesc.object()) {
+ desc.object().set(nullptr);
+ return true;
+ }
+
+ // Step 11b.
+ if (!targetDesc.configurable()) {
+ return js::Throw(cx, id, JSMSG_CANT_REPORT_NC_AS_NE);
+ }
+
+ // Steps 11c-d.
+ bool extensibleTarget;
+ if (!IsExtensible(cx, target, &extensibleTarget)) {
+ return false;
+ }
+
+ // Step 11e.
+ if (!extensibleTarget) {
+ return js::Throw(cx, id, JSMSG_CANT_REPORT_E_AS_NE);
+ }
+
+ // Step 11f.
+ desc.object().set(nullptr);
+ return true;
+ }
+
+ // Step 12.
+ bool extensibleTarget;
+ if (!IsExtensible(cx, target, &extensibleTarget)) {
+ return false;
+ }
+
+ // Step 13.
+ Rooted<PropertyDescriptor> resultDesc(cx);
+ if (!ToPropertyDescriptor(cx, trapResult, true, &resultDesc)) {
+ return false;
+ }
+
+ // Step 14.
+ CompletePropertyDescriptor(&resultDesc);
+
+ // Step 15.
+ const char* errorDetails = nullptr;
+ if (!IsCompatiblePropertyDescriptor(cx, extensibleTarget, resultDesc,
+ targetDesc, &errorDetails))
+ return false;
+
+ // Step 16.
+ if (errorDetails) {
+ return js::Throw(cx, id, JSMSG_CANT_REPORT_INVALID, errorDetails);
+ }
+
+ // Step 17.
+ if (!resultDesc.configurable()) {
+ if (!targetDesc.object()) {
+ return js::Throw(cx, id, JSMSG_CANT_REPORT_NE_AS_NC);
+ }
+
+ if (targetDesc.configurable()) {
+ return js::Throw(cx, id, JSMSG_CANT_REPORT_C_AS_NC);
+ }
+
+ if (resultDesc.hasWritable() && !resultDesc.writable()) {
+ if (targetDesc.writable()) {
+ return js::Throw(cx, id, JSMSG_CANT_REPORT_W_AS_NW);
+ }
+ }
+ }
+
+ // Step 18.
+ desc.set(resultDesc);
+ desc.object().set(proxy);
+ return true;
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 9.5.6 Proxy.[[DefineOwnProperty]](P, Desc)
+bool ScriptedProxyHandler::defineProperty(JSContext* cx, HandleObject proxy,
+ HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const {
+ // Steps 2-4.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 5.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 6.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().defineProperty, &trap)) {
+ return false;
+ }
+
+ // Step 7.
+ if (trap.isUndefined()) {
+ return DefineProperty(cx, target, id, desc, result);
+ }
+
+ // Step 8.
+ RootedValue descObj(cx);
+ if (!FromPropertyDescriptorToObject(cx, desc, &descObj)) {
+ return false;
+ }
+
+ // Step 9.
+ RootedValue propKey(cx);
+ if (!IdToStringOrSymbol(cx, id, &propKey)) {
+ return false;
+ }
+
+ RootedValue trapResult(cx);
+ {
+ FixedInvokeArgs<3> args(cx);
+
+ args[0].setObject(*target);
+ args[1].set(propKey);
+ args[2].set(descObj);
+
+ RootedValue thisv(cx, ObjectValue(*handler));
+ if (!Call(cx, trap, thisv, args, &trapResult)) {
+ return false;
+ }
+ }
+
+ // Step 10.
+ if (!ToBoolean(trapResult)) {
+ return result.fail(JSMSG_PROXY_DEFINE_RETURNED_FALSE);
+ }
+
+ // Step 11.
+ Rooted<PropertyDescriptor> targetDesc(cx);
+ if (!GetOwnPropertyDescriptor(cx, target, id, &targetDesc)) {
+ return false;
+ }
+
+ // Step 12.
+ bool extensibleTarget;
+ if (!IsExtensible(cx, target, &extensibleTarget)) {
+ return false;
+ }
+
+ // Steps 13-14.
+ bool settingConfigFalse = desc.hasConfigurable() && !desc.configurable();
+
+ // Steps 15-16.
+ if (!targetDesc.object()) {
+ // Step 15a.
+ if (!extensibleTarget) {
+ return js::Throw(cx, id, JSMSG_CANT_DEFINE_NEW);
+ }
+
+ // Step 15b.
+ if (settingConfigFalse) {
+ return js::Throw(cx, id, JSMSG_CANT_DEFINE_NE_AS_NC);
+ }
+ } else {
+ // Step 16a.
+ const char* errorDetails = nullptr;
+ if (!IsCompatiblePropertyDescriptor(cx, extensibleTarget, desc, targetDesc,
+ &errorDetails))
+ return false;
+
+ if (errorDetails) {
+ return js::Throw(cx, id, JSMSG_CANT_DEFINE_INVALID, errorDetails);
+ }
+
+ // Step 16b.
+ if (settingConfigFalse && targetDesc.configurable()) {
+ static const char DETAILS_CANT_REPORT_C_AS_NC[] =
+ "proxy can't define an existing configurable property as "
+ "non-configurable";
+ return js::Throw(cx, id, JSMSG_CANT_DEFINE_INVALID,
+ DETAILS_CANT_REPORT_C_AS_NC);
+ }
+
+ if (targetDesc.isDataDescriptor() && !targetDesc.configurable() &&
+ targetDesc.writable()) {
+ if (desc.hasWritable() && !desc.writable()) {
+ static const char DETAILS_CANT_DEFINE_NW[] =
+ "proxy can't define an existing non-configurable writable property "
+ "as non-writable";
+ return js::Throw(cx, id, JSMSG_CANT_DEFINE_INVALID,
+ DETAILS_CANT_DEFINE_NW);
+ }
+ }
+ }
+
+ // Step 17.
+ return result.succeed();
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 7.3.17 CreateListFromArrayLike with elementTypes fixed to symbol/string.
+static bool CreateFilteredListFromArrayLike(JSContext* cx, HandleValue v,
+ MutableHandleIdVector props) {
+ // Step 2.
+ RootedObject obj(cx, RequireObject(cx, JSMSG_OBJECT_REQUIRED_RET_OWNKEYS,
+ JSDVG_IGNORE_STACK, v));
+ if (!obj) {
+ return false;
+ }
+
+ // Step 3.
+ uint32_t len;
+ if (!GetLengthProperty(cx, obj, &len)) {
+ return false;
+ }
+
+ // Steps 4-6.
+ RootedValue next(cx);
+ RootedId id(cx);
+ uint32_t index = 0;
+ while (index < len) {
+ // Steps 6a-b.
+ if (!GetElement(cx, obj, obj, index, &next)) {
+ return false;
+ }
+
+ // Step 6c.
+ if (!next.isString() && !next.isSymbol()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_OWNKEYS_STR_SYM);
+ return false;
+ }
+
+ if (!PrimitiveValueToId<CanGC>(cx, next, &id)) {
+ return false;
+ }
+
+ // Step 6d.
+ if (!props.append(id)) {
+ return false;
+ }
+
+ // Step 6e.
+ index++;
+ }
+
+ // Step 7.
+ return true;
+}
+
+// ES2018 draft rev aab1ea3bd4d03c85d6f4a91503b4169346ab7271
+// 9.5.11 Proxy.[[OwnPropertyKeys]]()
+bool ScriptedProxyHandler::ownPropertyKeys(JSContext* cx, HandleObject proxy,
+ MutableHandleIdVector props) const {
+ // Steps 1-3.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 4.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 5.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().ownKeys, &trap)) {
+ return false;
+ }
+
+ // Step 6.
+ if (trap.isUndefined()) {
+ return GetPropertyKeys(
+ cx, target, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props);
+ }
+
+ // Step 7.
+ RootedValue trapResultArray(cx);
+ RootedValue targetVal(cx, ObjectValue(*target));
+ if (!Call(cx, trap, handler, targetVal, &trapResultArray)) {
+ return false;
+ }
+
+ // Step 8.
+ RootedIdVector trapResult(cx);
+ if (!CreateFilteredListFromArrayLike(cx, trapResultArray, &trapResult)) {
+ return false;
+ }
+
+ // Steps 9, 18.
+ Rooted<GCHashSet<jsid>> uncheckedResultKeys(
+ cx, GCHashSet<jsid>(cx, trapResult.length()));
+
+ for (size_t i = 0, len = trapResult.length(); i < len; i++) {
+ MOZ_ASSERT(!JSID_IS_VOID(trapResult[i]));
+
+ auto ptr = uncheckedResultKeys.lookupForAdd(trapResult[i]);
+ if (ptr) {
+ return js::Throw(cx, trapResult[i], JSMSG_OWNKEYS_DUPLICATE);
+ }
+
+ if (!uncheckedResultKeys.add(ptr, trapResult[i])) {
+ return false;
+ }
+ }
+
+ // Step 10.
+ bool extensibleTarget;
+ if (!IsExtensible(cx, target, &extensibleTarget)) {
+ return false;
+ }
+
+ // Steps 11-13.
+ RootedIdVector targetKeys(cx);
+ if (!GetPropertyKeys(cx, target,
+ JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
+ &targetKeys)) {
+ return false;
+ }
+
+ // Steps 14-15.
+ RootedIdVector targetConfigurableKeys(cx);
+ RootedIdVector targetNonconfigurableKeys(cx);
+
+ // Step 16.
+ Rooted<PropertyDescriptor> desc(cx);
+ for (size_t i = 0; i < targetKeys.length(); ++i) {
+ // Step 16.a.
+ if (!GetOwnPropertyDescriptor(cx, target, targetKeys[i], &desc)) {
+ return false;
+ }
+
+ // Steps 16.b-c.
+ if (desc.object() && !desc.configurable()) {
+ if (!targetNonconfigurableKeys.append(targetKeys[i])) {
+ return false;
+ }
+ } else {
+ if (!targetConfigurableKeys.append(targetKeys[i])) {
+ return false;
+ }
+ }
+ }
+
+ // Step 17.
+ if (extensibleTarget && targetNonconfigurableKeys.empty()) {
+ return props.appendAll(std::move(trapResult));
+ }
+
+ // Step 19.
+ for (size_t i = 0; i < targetNonconfigurableKeys.length(); ++i) {
+ MOZ_ASSERT(!JSID_IS_VOID(targetNonconfigurableKeys[i]));
+
+ auto ptr = uncheckedResultKeys.lookup(targetNonconfigurableKeys[i]);
+
+ // Step 19.a.
+ if (!ptr) {
+ return js::Throw(cx, targetNonconfigurableKeys[i], JSMSG_CANT_SKIP_NC);
+ }
+
+ // Step 19.b.
+ uncheckedResultKeys.remove(ptr);
+ }
+
+ // Step 20.
+ if (extensibleTarget) {
+ return props.appendAll(std::move(trapResult));
+ }
+
+ // Step 21.
+ for (size_t i = 0; i < targetConfigurableKeys.length(); ++i) {
+ MOZ_ASSERT(!JSID_IS_VOID(targetConfigurableKeys[i]));
+
+ auto ptr = uncheckedResultKeys.lookup(targetConfigurableKeys[i]);
+
+ // Step 21.a.
+ if (!ptr) {
+ return js::Throw(cx, targetConfigurableKeys[i],
+ JSMSG_CANT_REPORT_E_AS_NE);
+ }
+
+ // Step 21.b.
+ uncheckedResultKeys.remove(ptr);
+ }
+
+ // Step 22.
+ if (!uncheckedResultKeys.empty()) {
+ RootedId id(cx, uncheckedResultKeys.all().front());
+ return js::Throw(cx, id, JSMSG_CANT_REPORT_NEW);
+ }
+
+ // Step 23.
+ return props.appendAll(std::move(trapResult));
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 9.5.10 Proxy.[[Delete]](P)
+bool ScriptedProxyHandler::delete_(JSContext* cx, HandleObject proxy,
+ HandleId id, ObjectOpResult& result) const {
+ // Steps 2-4.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 5.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 6.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().deleteProperty, &trap)) {
+ return false;
+ }
+
+ // Step 7.
+ if (trap.isUndefined()) {
+ return DeleteProperty(cx, target, id, result);
+ }
+
+ // Step 8.
+ bool booleanTrapResult;
+ {
+ RootedValue value(cx);
+ if (!IdToStringOrSymbol(cx, id, &value)) {
+ return false;
+ }
+
+ RootedValue targetVal(cx, ObjectValue(*target));
+ RootedValue trapResult(cx);
+ if (!Call(cx, trap, handler, targetVal, value, &trapResult)) {
+ return false;
+ }
+
+ booleanTrapResult = ToBoolean(trapResult);
+ }
+
+ // Step 9.
+ if (!booleanTrapResult) {
+ return result.fail(JSMSG_PROXY_DELETE_RETURNED_FALSE);
+ }
+
+ // Step 10.
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!GetOwnPropertyDescriptor(cx, target, id, &desc)) {
+ return false;
+ }
+
+ // Step 11.
+ if (!desc.object()) {
+ return result.succeed();
+ }
+
+ // Step 12.
+ if (!desc.configurable()) {
+ return Throw(cx, id, JSMSG_CANT_DELETE);
+ }
+
+ bool extensible;
+ if (!IsExtensible(cx, target, &extensible)) {
+ return false;
+ }
+
+ if (!extensible) {
+ return Throw(cx, id, JSMSG_CANT_DELETE_NON_EXTENSIBLE);
+ }
+
+ // Step 13.
+ return result.succeed();
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 9.5.7 Proxy.[[HasProperty]](P)
+bool ScriptedProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id,
+ bool* bp) const {
+ // Steps 2-4.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 5.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 6.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().has, &trap)) {
+ return false;
+ }
+
+ // Step 7.
+ if (trap.isUndefined()) {
+ return HasProperty(cx, target, id, bp);
+ }
+
+ // Step 8.
+ RootedValue value(cx);
+ if (!IdToStringOrSymbol(cx, id, &value)) {
+ return false;
+ }
+
+ RootedValue trapResult(cx);
+ RootedValue targetVal(cx, ObjectValue(*target));
+ if (!Call(cx, trap, handler, targetVal, value, &trapResult)) {
+ return false;
+ }
+
+ bool booleanTrapResult = ToBoolean(trapResult);
+
+ // Step 9.
+ if (!booleanTrapResult) {
+ // Step 9a.
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!GetOwnPropertyDescriptor(cx, target, id, &desc)) {
+ return false;
+ }
+
+ // Step 9b.
+ if (desc.object()) {
+ // Step 9b(i).
+ if (!desc.configurable()) {
+ return js::Throw(cx, id, JSMSG_CANT_REPORT_NC_AS_NE);
+ }
+
+ // Step 9b(ii).
+ bool extensible;
+ if (!IsExtensible(cx, target, &extensible)) {
+ return false;
+ }
+
+ // Step 9b(iii).
+ if (!extensible) {
+ return js::Throw(cx, id, JSMSG_CANT_REPORT_E_AS_NE);
+ }
+ }
+ }
+
+ // Step 10.
+ *bp = booleanTrapResult;
+ return true;
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 9.5.8 Proxy.[[GetP]](P, Receiver)
+bool ScriptedProxyHandler::get(JSContext* cx, HandleObject proxy,
+ HandleValue receiver, HandleId id,
+ MutableHandleValue vp) const {
+ // Steps 2-4.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 5.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Steps 6.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().get, &trap)) {
+ return false;
+ }
+
+ // Step 7.
+ if (trap.isUndefined()) {
+ return GetProperty(cx, target, receiver, id, vp);
+ }
+
+ // Step 8.
+ RootedValue value(cx);
+ if (!IdToStringOrSymbol(cx, id, &value)) {
+ return false;
+ }
+
+ RootedValue trapResult(cx);
+ {
+ FixedInvokeArgs<3> args(cx);
+
+ args[0].setObject(*target);
+ args[1].set(value);
+ args[2].set(receiver);
+
+ RootedValue thisv(cx, ObjectValue(*handler));
+ if (!Call(cx, trap, thisv, args, &trapResult)) {
+ return false;
+ }
+ }
+
+ // Step 9.
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!GetOwnPropertyDescriptor(cx, target, id, &desc)) {
+ return false;
+ }
+
+ // Step 10.
+ if (desc.object()) {
+ // Step 10a.
+ if (desc.isDataDescriptor() && !desc.configurable() && !desc.writable()) {
+ bool same;
+ if (!SameValue(cx, trapResult, desc.value(), &same)) {
+ return false;
+ }
+ if (!same) {
+ return js::Throw(cx, id, JSMSG_MUST_REPORT_SAME_VALUE);
+ }
+ }
+
+ // Step 10b.
+ if (desc.isAccessorDescriptor() && !desc.configurable() &&
+ (desc.getterObject() == nullptr) && !trapResult.isUndefined()) {
+ return js::Throw(cx, id, JSMSG_MUST_REPORT_UNDEFINED);
+ }
+ }
+
+ // Step 11.
+ vp.set(trapResult);
+ return true;
+}
+
+// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
+// 9.5.9 Proxy.[[Set]](P, V, Receiver)
+bool ScriptedProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id,
+ HandleValue v, HandleValue receiver,
+ ObjectOpResult& result) const {
+ // Steps 2-4.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 5.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+
+ // Step 6.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().set, &trap)) {
+ return false;
+ }
+
+ // Step 7.
+ if (trap.isUndefined()) {
+ return SetProperty(cx, target, id, v, receiver, result);
+ }
+
+ // Step 8.
+ RootedValue value(cx);
+ if (!IdToStringOrSymbol(cx, id, &value)) {
+ return false;
+ }
+
+ RootedValue trapResult(cx);
+ {
+ FixedInvokeArgs<4> args(cx);
+
+ args[0].setObject(*target);
+ args[1].set(value);
+ args[2].set(v);
+ args[3].set(receiver);
+
+ RootedValue thisv(cx, ObjectValue(*handler));
+ if (!Call(cx, trap, thisv, args, &trapResult)) {
+ return false;
+ }
+ }
+
+ // Step 9.
+ if (!ToBoolean(trapResult)) {
+ return result.fail(JSMSG_PROXY_SET_RETURNED_FALSE);
+ }
+
+ // Step 10.
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!GetOwnPropertyDescriptor(cx, target, id, &desc)) {
+ return false;
+ }
+
+ // Step 11.
+ if (desc.object()) {
+ // Step 11a.
+ if (desc.isDataDescriptor() && !desc.configurable() && !desc.writable()) {
+ bool same;
+ if (!SameValue(cx, v, desc.value(), &same)) {
+ return false;
+ }
+ if (!same) {
+ return js::Throw(cx, id, JSMSG_CANT_SET_NW_NC);
+ }
+ }
+
+ // Step 11b.
+ if (desc.isAccessorDescriptor() && !desc.configurable() &&
+ desc.setterObject() == nullptr) {
+ return js::Throw(cx, id, JSMSG_CANT_SET_WO_SETTER);
+ }
+ }
+
+ // Step 12.
+ return result.succeed();
+}
+
+// ES7 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.13 Proxy.[[Call]]
+bool ScriptedProxyHandler::call(JSContext* cx, HandleObject proxy,
+ const CallArgs& args) const {
+ // Steps 1-3.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 4.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+ MOZ_ASSERT(target->isCallable());
+
+ // Step 5.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().apply, &trap)) {
+ return false;
+ }
+
+ // Step 6.
+ if (trap.isUndefined()) {
+ InvokeArgs iargs(cx);
+ if (!FillArgumentsFromArraylike(cx, iargs, args)) {
+ return false;
+ }
+
+ RootedValue fval(cx, ObjectValue(*target));
+ return js::Call(cx, fval, args.thisv(), iargs, args.rval());
+ }
+
+ // Step 7.
+ RootedObject argArray(cx,
+ NewDenseCopiedArray(cx, args.length(), args.array()));
+ if (!argArray) {
+ return false;
+ }
+
+ // Step 8.
+ FixedInvokeArgs<3> iargs(cx);
+
+ iargs[0].setObject(*target);
+ iargs[1].set(args.thisv());
+ iargs[2].setObject(*argArray);
+
+ RootedValue thisv(cx, ObjectValue(*handler));
+ return js::Call(cx, trap, thisv, iargs, args.rval());
+}
+
+// ES7 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.14 Proxy.[[Construct]]
+bool ScriptedProxyHandler::construct(JSContext* cx, HandleObject proxy,
+ const CallArgs& args) const {
+ // Steps 1-3.
+ RootedObject handler(cx, ScriptedProxyHandler::handlerObject(proxy));
+ if (!handler) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_REVOKED);
+ return false;
+ }
+
+ // Step 4.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ MOZ_ASSERT(target);
+ MOZ_ASSERT(target->isConstructor());
+
+ // Step 5.
+ RootedValue trap(cx);
+ if (!GetProxyTrap(cx, handler, cx->names().construct, &trap)) {
+ return false;
+ }
+
+ // Step 6.
+ if (trap.isUndefined()) {
+ ConstructArgs cargs(cx);
+ if (!FillArgumentsFromArraylike(cx, cargs, args)) {
+ return false;
+ }
+
+ RootedValue targetv(cx, ObjectValue(*target));
+ RootedObject obj(cx);
+ if (!Construct(cx, targetv, cargs, args.newTarget(), &obj)) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ // Step 7.
+ RootedObject argArray(cx,
+ NewDenseCopiedArray(cx, args.length(), args.array()));
+ if (!argArray) {
+ return false;
+ }
+
+ // Steps 8, 10.
+ {
+ FixedInvokeArgs<3> iargs(cx);
+
+ iargs[0].setObject(*target);
+ iargs[1].setObject(*argArray);
+ iargs[2].set(args.newTarget());
+
+ RootedValue thisv(cx, ObjectValue(*handler));
+ if (!Call(cx, trap, thisv, iargs, args.rval())) {
+ return false;
+ }
+ }
+
+ // Step 9.
+ if (!args.rval().isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_PROXY_CONSTRUCT_OBJECT);
+ return false;
+ }
+
+ return true;
+}
+
+bool ScriptedProxyHandler::nativeCall(JSContext* cx, IsAcceptableThis test,
+ NativeImpl impl,
+ const CallArgs& args) const {
+ ReportIncompatible(cx, args);
+ return false;
+}
+
+bool ScriptedProxyHandler::hasInstance(JSContext* cx, HandleObject proxy,
+ MutableHandleValue v, bool* bp) const {
+ return InstanceofOperator(cx, proxy, v, bp);
+}
+
+bool ScriptedProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
+ ESClass* cls) const {
+ *cls = ESClass::Other;
+ return true;
+}
+
+bool ScriptedProxyHandler::isArray(JSContext* cx, HandleObject proxy,
+ IsArrayAnswer* answer) const {
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ if (target) {
+ return JS::IsArray(cx, target, answer);
+ }
+
+ *answer = IsArrayAnswer::RevokedProxy;
+ return true;
+}
+
+const char* ScriptedProxyHandler::className(JSContext* cx,
+ HandleObject proxy) const {
+ // Right now the caller is not prepared to handle failures.
+ return BaseProxyHandler::className(cx, proxy);
+}
+
+JSString* ScriptedProxyHandler::fun_toString(JSContext* cx, HandleObject proxy,
+ bool isToSource) const {
+ // The BaseProxyHandler has the desired behavior: Throw for non-callable,
+ // otherwise return [native code].
+ return BaseProxyHandler::fun_toString(cx, proxy, isToSource);
+}
+
+RegExpShared* ScriptedProxyHandler::regexp_toShared(JSContext* cx,
+ HandleObject proxy) const {
+ MOZ_CRASH("Should not end up in ScriptedProxyHandler::regexp_toShared");
+}
+
+bool ScriptedProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy,
+ MutableHandleValue vp) const {
+ MOZ_CRASH("Should not end up in ScriptedProxyHandler::boxedValue_unbox");
+ return false;
+}
+
+bool ScriptedProxyHandler::isCallable(JSObject* obj) const {
+ MOZ_ASSERT(obj->as<ProxyObject>().handler() ==
+ &ScriptedProxyHandler::singleton);
+ uint32_t callConstruct = obj->as<ProxyObject>()
+ .reservedSlot(IS_CALLCONSTRUCT_EXTRA)
+ .toPrivateUint32();
+ return !!(callConstruct & IS_CALLABLE);
+}
+
+bool ScriptedProxyHandler::isConstructor(JSObject* obj) const {
+ MOZ_ASSERT(obj->as<ProxyObject>().handler() ==
+ &ScriptedProxyHandler::singleton);
+ uint32_t callConstruct = obj->as<ProxyObject>()
+ .reservedSlot(IS_CALLCONSTRUCT_EXTRA)
+ .toPrivateUint32();
+ return !!(callConstruct & IS_CONSTRUCTOR);
+}
+
+const char ScriptedProxyHandler::family = 0;
+const ScriptedProxyHandler ScriptedProxyHandler::singleton;
+
+// ES2021 rev c21b280a2c46e92decf3efeca9e9da35d5b9f622
+// Including the changes from: https://github.com/tc39/ecma262/pull/1814
+// 9.5.14 ProxyCreate.
+static bool ProxyCreate(JSContext* cx, CallArgs& args, const char* callerName) {
+ if (!args.requireAtLeast(cx, callerName, 2)) {
+ return false;
+ }
+
+ // Step 1.
+ RootedObject target(cx,
+ RequireObjectArg(cx, "`target`", callerName, args[0]));
+ if (!target) {
+ return false;
+ }
+
+ // Step 2.
+ RootedObject handler(cx,
+ RequireObjectArg(cx, "`handler`", callerName, args[1]));
+ if (!handler) {
+ return false;
+ }
+
+ // Steps 3-4, 6.
+ RootedValue priv(cx, ObjectValue(*target));
+ JSObject* proxy_ = NewProxyObject(cx, &ScriptedProxyHandler::singleton, priv,
+ TaggedProto::LazyProto);
+ if (!proxy_) {
+ return false;
+ }
+
+ // Step 7 (reordered).
+ Rooted<ProxyObject*> proxy(cx, &proxy_->as<ProxyObject>());
+ proxy->setReservedSlot(ScriptedProxyHandler::HANDLER_EXTRA,
+ ObjectValue(*handler));
+
+ // Step 5.
+ uint32_t callable =
+ target->isCallable() ? ScriptedProxyHandler::IS_CALLABLE : 0;
+ uint32_t constructor =
+ target->isConstructor() ? ScriptedProxyHandler::IS_CONSTRUCTOR : 0;
+ proxy->setReservedSlot(ScriptedProxyHandler::IS_CALLCONSTRUCT_EXTRA,
+ PrivateUint32Value(callable | constructor));
+
+ // Step 8.
+ args.rval().setObject(*proxy);
+ return true;
+}
+
+bool js::proxy(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "Proxy")) {
+ return false;
+ }
+
+ return ProxyCreate(cx, args, "Proxy");
+}
+
+static bool RevokeProxy(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedFunction func(cx, &args.callee().as<JSFunction>());
+ RootedObject p(cx, func->getExtendedSlot(ScriptedProxyHandler::REVOKE_SLOT)
+ .toObjectOrNull());
+
+ if (p) {
+ func->setExtendedSlot(ScriptedProxyHandler::REVOKE_SLOT, NullValue());
+
+ MOZ_ASSERT(p->is<ProxyObject>());
+
+ p->as<ProxyObject>().setSameCompartmentPrivate(NullValue());
+ p->as<ProxyObject>().setReservedSlot(ScriptedProxyHandler::HANDLER_EXTRA,
+ NullValue());
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+bool js::proxy_revocable(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ProxyCreate(cx, args, "Proxy.revocable")) {
+ return false;
+ }
+
+ RootedValue proxyVal(cx, args.rval());
+ MOZ_ASSERT(proxyVal.toObject().is<ProxyObject>());
+
+ RootedFunction revoker(
+ cx, NewNativeFunction(cx, RevokeProxy, 0, nullptr,
+ gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
+ if (!revoker) {
+ return false;
+ }
+
+ revoker->initExtendedSlot(ScriptedProxyHandler::REVOKE_SLOT, proxyVal);
+
+ RootedPlainObject result(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!result) {
+ return false;
+ }
+
+ RootedValue revokeVal(cx, ObjectValue(*revoker));
+ if (!DefineDataProperty(cx, result, cx->names().proxy, proxyVal) ||
+ !DefineDataProperty(cx, result, cx->names().revoke, revokeVal)) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
diff --git a/js/src/proxy/ScriptedProxyHandler.h b/js/src/proxy/ScriptedProxyHandler.h
new file mode 100644
index 0000000000..013e1e03c2
--- /dev/null
+++ b/js/src/proxy/ScriptedProxyHandler.h
@@ -0,0 +1,116 @@
+/* -*- 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/. */
+
+#ifndef proxy_ScriptedProxyHandler_h
+#define proxy_ScriptedProxyHandler_h
+
+#include "js/Proxy.h"
+
+namespace js {
+
+/* Derived class for all scripted proxy handlers. */
+class ScriptedProxyHandler : public BaseProxyHandler {
+ public:
+ constexpr ScriptedProxyHandler() : BaseProxyHandler(&family) {}
+
+ /* Standard internal methods. */
+ virtual bool getOwnPropertyDescriptor(
+ JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
+ JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
+ virtual bool defineProperty(JSContext* cx, JS::HandleObject proxy,
+ JS::HandleId id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult& result) const override;
+ virtual bool ownPropertyKeys(JSContext* cx, JS::HandleObject proxy,
+ JS::MutableHandleIdVector props) const override;
+ virtual bool delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
+ JS::ObjectOpResult& result) const override;
+
+ virtual bool getPrototype(JSContext* cx, JS::HandleObject proxy,
+ JS::MutableHandleObject protop) const override;
+ virtual bool setPrototype(JSContext* cx, JS::HandleObject proxy,
+ JS::HandleObject proto,
+ JS::ObjectOpResult& result) const override;
+ /* Non-standard, but needed to correctly implement OrdinaryGetPrototypeOf. */
+ virtual bool getPrototypeIfOrdinary(
+ JSContext* cx, JS::HandleObject proxy, bool* isOrdinary,
+ JS::MutableHandleObject protop) const override;
+ /* Non-standard, but needed to handle revoked proxies. */
+ virtual bool setImmutablePrototype(JSContext* cx, JS::HandleObject proxy,
+ bool* succeeded) const override;
+
+ virtual bool preventExtensions(JSContext* cx, JS::HandleObject proxy,
+ JS::ObjectOpResult& result) const override;
+ virtual bool isExtensible(JSContext* cx, JS::HandleObject proxy,
+ bool* extensible) const override;
+
+ virtual bool has(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
+ bool* bp) const override;
+ virtual bool get(JSContext* cx, JS::HandleObject proxy,
+ JS::HandleValue receiver, JS::HandleId id,
+ JS::MutableHandleValue vp) const override;
+ virtual bool set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
+ JS::HandleValue v, JS::HandleValue receiver,
+ JS::ObjectOpResult& result) const override;
+ virtual bool call(JSContext* cx, JS::HandleObject proxy,
+ const JS::CallArgs& args) const override;
+ virtual bool construct(JSContext* cx, JS::HandleObject proxy,
+ const JS::CallArgs& args) const override;
+
+ /* SpiderMonkey extensions. */
+ virtual bool hasOwn(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
+ bool* bp) const override {
+ return BaseProxyHandler::hasOwn(cx, proxy, id, bp);
+ }
+
+ // A scripted proxy should not be treated as generic in most contexts.
+ virtual bool nativeCall(JSContext* cx, JS::IsAcceptableThis test,
+ JS::NativeImpl impl,
+ const JS::CallArgs& args) const override;
+ virtual bool hasInstance(JSContext* cx, JS::HandleObject proxy,
+ JS::MutableHandleValue v, bool* bp) const override;
+ virtual bool getBuiltinClass(JSContext* cx, JS::HandleObject proxy,
+ ESClass* cls) const override;
+ virtual bool isArray(JSContext* cx, JS::HandleObject proxy,
+ JS::IsArrayAnswer* answer) const override;
+ virtual const char* className(JSContext* cx,
+ JS::HandleObject proxy) const override;
+ virtual JSString* fun_toString(JSContext* cx, JS::HandleObject proxy,
+ bool isToSource) const override;
+ virtual RegExpShared* regexp_toShared(JSContext* cx,
+ JS::HandleObject proxy) const override;
+ virtual bool boxedValue_unbox(JSContext* cx, JS::HandleObject proxy,
+ JS::MutableHandleValue vp) const override;
+
+ virtual bool isCallable(JSObject* obj) const override;
+ virtual bool isConstructor(JSObject* obj) const override;
+
+ virtual bool isScripted() const override { return true; }
+
+ static const char family;
+ static const ScriptedProxyHandler singleton;
+
+ // The "proxy extra" slot index in which the handler is stored. Revocable
+ // proxies need to set this at revocation time.
+ static const int HANDLER_EXTRA = 0;
+ static const int IS_CALLCONSTRUCT_EXTRA = 1;
+ // Bitmasks for the "call/construct" slot
+ static const int IS_CALLABLE = 1 << 0;
+ static const int IS_CONSTRUCTOR = 1 << 1;
+ // The "function extended" slot index in which the revocation object is
+ // stored. Per spec, this is to be cleared during the first revocation.
+ static const int REVOKE_SLOT = 0;
+
+ static JSObject* handlerObject(const JSObject* proxy);
+};
+
+bool proxy(JSContext* cx, unsigned argc, JS::Value* vp);
+
+bool proxy_revocable(JSContext* cx, unsigned argc, JS::Value* vp);
+
+} /* namespace js */
+
+#endif /* proxy_ScriptedProxyHandler_h */
diff --git a/js/src/proxy/SecurityWrapper.cpp b/js/src/proxy/SecurityWrapper.cpp
new file mode 100644
index 0000000000..1b8ce9677c
--- /dev/null
+++ b/js/src/proxy/SecurityWrapper.cpp
@@ -0,0 +1,110 @@
+/* -*- 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 "jsapi.h"
+#include "jsfriendapi.h"
+#include "NamespaceImports.h"
+
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "js/Wrapper.h"
+#include "vm/JSObject.h"
+#include "vm/StringType.h"
+
+using namespace js;
+
+template <class Base>
+bool SecurityWrapper<Base>::enter(JSContext* cx, HandleObject wrapper,
+ HandleId id, Wrapper::Action act,
+ bool mayThrow, bool* bp) const {
+ ReportAccessDenied(cx);
+ *bp = false;
+ return false;
+}
+
+template <class Base>
+bool SecurityWrapper<Base>::nativeCall(JSContext* cx, IsAcceptableThis test,
+ NativeImpl impl,
+ const CallArgs& args) const {
+ ReportAccessDenied(cx);
+ return false;
+}
+
+template <class Base>
+bool SecurityWrapper<Base>::setPrototype(JSContext* cx, HandleObject wrapper,
+ HandleObject proto,
+ ObjectOpResult& result) const {
+ ReportAccessDenied(cx);
+ return false;
+}
+
+template <class Base>
+bool SecurityWrapper<Base>::setImmutablePrototype(JSContext* cx,
+ HandleObject wrapper,
+ bool* succeeded) const {
+ ReportAccessDenied(cx);
+ return false;
+}
+
+template <class Base>
+bool SecurityWrapper<Base>::preventExtensions(JSContext* cx,
+ HandleObject wrapper,
+ ObjectOpResult& result) const {
+ // Just like BaseProxyHandler, SecurityWrappers claim by default to always
+ // be extensible, so as not to leak information about the state of the
+ // underlying wrapped thing.
+ return result.fail(JSMSG_CANT_CHANGE_EXTENSIBILITY);
+}
+
+template <class Base>
+bool SecurityWrapper<Base>::isExtensible(JSContext* cx, HandleObject wrapper,
+ bool* extensible) const {
+ // See above.
+ *extensible = true;
+ return true;
+}
+
+template <class Base>
+bool SecurityWrapper<Base>::getBuiltinClass(JSContext* cx, HandleObject wrapper,
+ ESClass* cls) const {
+ *cls = ESClass::Other;
+ return true;
+}
+
+template <class Base>
+bool SecurityWrapper<Base>::isArray(JSContext* cx, HandleObject obj,
+ JS::IsArrayAnswer* answer) const {
+ // This should ReportAccessDenied(cx), but bug 849730 disagrees. :-(
+ *answer = JS::IsArrayAnswer::NotArray;
+ return true;
+}
+
+template <class Base>
+RegExpShared* SecurityWrapper<Base>::regexp_toShared(JSContext* cx,
+ HandleObject obj) const {
+ return Base::regexp_toShared(cx, obj);
+}
+
+template <class Base>
+bool SecurityWrapper<Base>::boxedValue_unbox(JSContext* cx, HandleObject obj,
+ MutableHandleValue vp) const {
+ vp.setUndefined();
+ return true;
+}
+
+template <class Base>
+bool SecurityWrapper<Base>::defineProperty(JSContext* cx, HandleObject wrapper,
+ HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const {
+ if (desc.getter() || desc.setter()) {
+ return Throw(cx, id, JSMSG_ACCESSOR_DEF_DENIED);
+ }
+
+ return Base::defineProperty(cx, wrapper, id, desc, result);
+}
+
+template class js::SecurityWrapper<Wrapper>;
+template class js::SecurityWrapper<CrossCompartmentWrapper>;
diff --git a/js/src/proxy/Wrapper.cpp b/js/src/proxy/Wrapper.cpp
new file mode 100644
index 0000000000..89eb37187d
--- /dev/null
+++ b/js/src/proxy/Wrapper.cpp
@@ -0,0 +1,473 @@
+/* -*- 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/Wrapper.h"
+
+#include "jsexn.h"
+
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/friend/WindowProxy.h" // js::IsWindowProxy
+#include "js/Object.h" // JS::GetBuiltinClass
+#include "js/Proxy.h"
+#include "vm/ErrorObject.h"
+#include "vm/JSContext.h"
+#include "vm/ProxyObject.h"
+#include "vm/Realm.h"
+#include "vm/RegExpObject.h"
+#include "vm/WrapperObject.h"
+
+#include "gc/Marking-inl.h"
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+bool Wrapper::finalizeInBackground(const Value& priv) const {
+ if (!priv.isObject()) {
+ return true;
+ }
+
+ /*
+ * Make the 'background-finalized-ness' of the wrapper the same as the
+ * wrapped object, to allow transplanting between them.
+ */
+ JSObject* wrapped = MaybeForwarded(&priv.toObject());
+ gc::AllocKind wrappedKind;
+ if (IsInsideNursery(wrapped)) {
+ JSRuntime* rt = wrapped->runtimeFromMainThread();
+ wrappedKind = wrapped->allocKindForTenure(rt->gc.nursery());
+ } else {
+ wrappedKind = wrapped->asTenured().getAllocKind();
+ }
+ return IsBackgroundFinalized(wrappedKind);
+}
+
+bool ForwardingProxyHandler::getOwnPropertyDescriptor(
+ JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const {
+ assertEnteredPolicy(cx, proxy, id, GET | SET | GET_PROPERTY_DESCRIPTOR);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetOwnPropertyDescriptor(cx, target, id, desc);
+}
+
+bool ForwardingProxyHandler::defineProperty(JSContext* cx, HandleObject proxy,
+ HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const {
+ assertEnteredPolicy(cx, proxy, id, SET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return DefineProperty(cx, target, id, desc, result);
+}
+
+bool ForwardingProxyHandler::ownPropertyKeys(
+ JSContext* cx, HandleObject proxy, MutableHandleIdVector props) const {
+ assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetPropertyKeys(
+ cx, target, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props);
+}
+
+bool ForwardingProxyHandler::delete_(JSContext* cx, HandleObject proxy,
+ HandleId id,
+ ObjectOpResult& result) const {
+ assertEnteredPolicy(cx, proxy, id, SET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return DeleteProperty(cx, target, id, result);
+}
+
+bool ForwardingProxyHandler::enumerate(JSContext* cx, HandleObject proxy,
+ MutableHandleIdVector props) const {
+ assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
+ MOZ_ASSERT(
+ !hasPrototype()); // Should never be called if there's a prototype.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return EnumerateProperties(cx, target, props);
+}
+
+bool ForwardingProxyHandler::getPrototype(JSContext* cx, HandleObject proxy,
+ MutableHandleObject protop) const {
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetPrototype(cx, target, protop);
+}
+
+bool ForwardingProxyHandler::setPrototype(JSContext* cx, HandleObject proxy,
+ HandleObject proto,
+ ObjectOpResult& result) const {
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return SetPrototype(cx, target, proto, result);
+}
+
+bool ForwardingProxyHandler::getPrototypeIfOrdinary(
+ JSContext* cx, HandleObject proxy, bool* isOrdinary,
+ MutableHandleObject protop) const {
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetPrototypeIfOrdinary(cx, target, isOrdinary, protop);
+}
+
+bool ForwardingProxyHandler::setImmutablePrototype(JSContext* cx,
+ HandleObject proxy,
+ bool* succeeded) const {
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return SetImmutablePrototype(cx, target, succeeded);
+}
+
+bool ForwardingProxyHandler::preventExtensions(JSContext* cx,
+ HandleObject proxy,
+ ObjectOpResult& result) const {
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return PreventExtensions(cx, target, result);
+}
+
+bool ForwardingProxyHandler::isExtensible(JSContext* cx, HandleObject proxy,
+ bool* extensible) const {
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return IsExtensible(cx, target, extensible);
+}
+
+bool ForwardingProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id,
+ bool* bp) const {
+ assertEnteredPolicy(cx, proxy, id, GET);
+ MOZ_ASSERT(
+ !hasPrototype()); // Should never be called if there's a prototype.
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return HasProperty(cx, target, id, bp);
+}
+
+bool ForwardingProxyHandler::get(JSContext* cx, HandleObject proxy,
+ HandleValue receiver, HandleId id,
+ MutableHandleValue vp) const {
+ assertEnteredPolicy(cx, proxy, id, GET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetProperty(cx, target, receiver, id, vp);
+}
+
+bool ForwardingProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id,
+ HandleValue v, HandleValue receiver,
+ ObjectOpResult& result) const {
+ assertEnteredPolicy(cx, proxy, id, SET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return SetProperty(cx, target, id, v, receiver, result);
+}
+
+bool ForwardingProxyHandler::call(JSContext* cx, HandleObject proxy,
+ const CallArgs& args) const {
+ assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
+ RootedValue target(cx, proxy->as<ProxyObject>().private_());
+
+ InvokeArgs iargs(cx);
+ if (!FillArgumentsFromArraylike(cx, iargs, args)) {
+ return false;
+ }
+
+ return js::Call(cx, target, args.thisv(), iargs, args.rval());
+}
+
+bool ForwardingProxyHandler::construct(JSContext* cx, HandleObject proxy,
+ const CallArgs& args) const {
+ assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
+
+ RootedValue target(cx, proxy->as<ProxyObject>().private_());
+ if (!IsConstructor(target)) {
+ ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, target,
+ nullptr);
+ return false;
+ }
+
+ ConstructArgs cargs(cx);
+ if (!FillArgumentsFromArraylike(cx, cargs, args)) {
+ return false;
+ }
+
+ RootedObject obj(cx);
+ if (!Construct(cx, target, cargs, args.newTarget(), &obj)) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+bool ForwardingProxyHandler::hasOwn(JSContext* cx, HandleObject proxy,
+ HandleId id, bool* bp) const {
+ assertEnteredPolicy(cx, proxy, id, GET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return HasOwnProperty(cx, target, id, bp);
+}
+
+bool ForwardingProxyHandler::getOwnEnumerablePropertyKeys(
+ JSContext* cx, HandleObject proxy, MutableHandleIdVector props) const {
+ assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetPropertyKeys(cx, target, JSITER_OWNONLY, props);
+}
+
+bool ForwardingProxyHandler::nativeCall(JSContext* cx, IsAcceptableThis test,
+ NativeImpl impl,
+ const CallArgs& args) const {
+ args.setThis(
+ ObjectValue(*args.thisv().toObject().as<ProxyObject>().target()));
+ if (!test(args.thisv())) {
+ ReportIncompatible(cx, args);
+ return false;
+ }
+
+ return CallNativeImpl(cx, impl, args);
+}
+
+bool ForwardingProxyHandler::hasInstance(JSContext* cx, HandleObject proxy,
+ MutableHandleValue v, bool* bp) const {
+ assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return HasInstance(cx, target, v, bp);
+}
+
+bool ForwardingProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
+ ESClass* cls) const {
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return JS::GetBuiltinClass(cx, target, cls);
+}
+
+bool ForwardingProxyHandler::isArray(JSContext* cx, HandleObject proxy,
+ JS::IsArrayAnswer* answer) const {
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return IsArray(cx, target, answer);
+}
+
+const char* ForwardingProxyHandler::className(JSContext* cx,
+ HandleObject proxy) const {
+ assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return GetObjectClassName(cx, target);
+}
+
+JSString* ForwardingProxyHandler::fun_toString(JSContext* cx,
+ HandleObject proxy,
+ bool isToSource) const {
+ assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return fun_toStringHelper(cx, target, isToSource);
+}
+
+RegExpShared* ForwardingProxyHandler::regexp_toShared(
+ JSContext* cx, HandleObject proxy) const {
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return RegExpToShared(cx, target);
+}
+
+bool ForwardingProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy,
+ MutableHandleValue vp) const {
+ RootedObject target(cx, proxy->as<ProxyObject>().target());
+ return Unbox(cx, target, vp);
+}
+
+bool ForwardingProxyHandler::isCallable(JSObject* obj) const {
+ JSObject* target = obj->as<ProxyObject>().target();
+ return target->isCallable();
+}
+
+bool ForwardingProxyHandler::isConstructor(JSObject* obj) const {
+ JSObject* target = obj->as<ProxyObject>().target();
+ return target->isConstructor();
+}
+
+JSObject* Wrapper::New(JSContext* cx, JSObject* obj, const Wrapper* handler,
+ const WrapperOptions& options) {
+ // If this is a cross-compartment wrapper allocate it in the compartment's
+ // first global. See Compartment::globalForNewCCW.
+ mozilla::Maybe<AutoRealm> ar;
+ if (handler->isCrossCompartmentWrapper()) {
+ ar.emplace(cx, &cx->compartment()->globalForNewCCW());
+ }
+ RootedValue priv(cx, ObjectValue(*obj));
+ return NewProxyObject(cx, handler, priv, options.proto(), options);
+}
+
+JSObject* Wrapper::NewSingleton(JSContext* cx, JSObject* obj,
+ const Wrapper* handler,
+ const WrapperOptions& options) {
+ // If this is a cross-compartment wrapper allocate it in the compartment's
+ // first global. See Compartment::globalForNewCCW.
+ mozilla::Maybe<AutoRealm> ar;
+ if (handler->isCrossCompartmentWrapper()) {
+ ar.emplace(cx, &cx->compartment()->globalForNewCCW());
+ }
+ RootedValue priv(cx, ObjectValue(*obj));
+ return NewProxyObject(cx, handler, priv, options.proto(), options);
+}
+
+JSObject* Wrapper::Renew(JSObject* existing, JSObject* obj,
+ const Wrapper* handler) {
+ existing->as<ProxyObject>().renew(handler, ObjectValue(*obj));
+ return existing;
+}
+
+JSObject* Wrapper::wrappedObject(JSObject* wrapper) {
+ MOZ_ASSERT(wrapper->is<WrapperObject>());
+ JSObject* target = wrapper->as<ProxyObject>().target();
+
+ if (target) {
+ // A cross-compartment wrapper should never wrap a CCW. We rely on this
+ // in the wrapper handlers (we use AutoRealm on our return value, and
+ // AutoRealm cannot be used with CCWs).
+ MOZ_ASSERT_IF(IsCrossCompartmentWrapper(wrapper),
+ !IsCrossCompartmentWrapper(target));
+
+#ifdef DEBUG
+ // An incremental GC will eventually mark the targets of black wrappers
+ // black but while it is in progress we can observe gray targets.
+ if (!wrapper->runtimeFromMainThread()->gc.isIncrementalGCInProgress() &&
+ wrapper->isMarkedBlack()) {
+ JS::AssertObjectIsNotGray(target);
+ }
+#endif
+
+ // Unmark wrapper targets that should be black in case an incremental GC
+ // hasn't marked them the correct color yet.
+ JS::ExposeObjectToActiveJS(target);
+ }
+
+ return target;
+}
+
+JS_FRIEND_API JSObject* js::UncheckedUnwrapWithoutExpose(JSObject* wrapped) {
+ while (true) {
+ if (!wrapped->is<WrapperObject>() || MOZ_UNLIKELY(IsWindowProxy(wrapped))) {
+ break;
+ }
+ wrapped = wrapped->as<WrapperObject>().target();
+
+ // This can be called from when getting a weakmap key delegate() on a
+ // wrapper whose referent has been moved while it is still unmarked.
+ if (wrapped) {
+ wrapped = MaybeForwarded(wrapped);
+ }
+ }
+ return wrapped;
+}
+
+JS_FRIEND_API JSObject* js::UncheckedUnwrap(JSObject* wrapped,
+ bool stopAtWindowProxy,
+ unsigned* flagsp) {
+ MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());
+ MOZ_ASSERT(CurrentThreadCanAccessRuntime(wrapped->runtimeFromAnyThread()));
+
+ unsigned flags = 0;
+ while (true) {
+ if (!wrapped->is<WrapperObject>() ||
+ MOZ_UNLIKELY(stopAtWindowProxy && IsWindowProxy(wrapped))) {
+ break;
+ }
+ flags |= Wrapper::wrapperHandler(wrapped)->flags();
+ wrapped = Wrapper::wrappedObject(wrapped);
+ }
+ if (flagsp) {
+ *flagsp = flags;
+ }
+ return wrapped;
+}
+
+JS_FRIEND_API JSObject* js::CheckedUnwrapStatic(JSObject* obj) {
+ while (true) {
+ JSObject* wrapper = obj;
+ obj = UnwrapOneCheckedStatic(obj);
+ if (!obj || obj == wrapper) {
+ return obj;
+ }
+ }
+}
+
+JS_FRIEND_API JSObject* js::UnwrapOneCheckedStatic(JSObject* obj) {
+ MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());
+ MOZ_ASSERT(CurrentThreadCanAccessRuntime(obj->runtimeFromAnyThread()));
+
+ // Note: callers that care about WindowProxy unwrapping should use
+ // CheckedUnwrapDynamic or UnwrapOneCheckedDynamic instead of this. We don't
+ // unwrap WindowProxy here to preserve legacy behavior and for consistency
+ // with CheckedUnwrapDynamic's default stopAtWindowProxy = true.
+ if (!obj->is<WrapperObject>() || MOZ_UNLIKELY(IsWindowProxy(obj))) {
+ return obj;
+ }
+
+ const Wrapper* handler = Wrapper::wrapperHandler(obj);
+ return handler->hasSecurityPolicy() ? nullptr : Wrapper::wrappedObject(obj);
+}
+
+JS_FRIEND_API JSObject* js::CheckedUnwrapDynamic(JSObject* obj, JSContext* cx,
+ bool stopAtWindowProxy) {
+ RootedObject wrapper(cx, obj);
+ while (true) {
+ JSObject* unwrapped =
+ UnwrapOneCheckedDynamic(wrapper, cx, stopAtWindowProxy);
+ if (!unwrapped || unwrapped == wrapper) {
+ return unwrapped;
+ }
+ wrapper = unwrapped;
+ }
+}
+
+JS_FRIEND_API JSObject* js::UnwrapOneCheckedDynamic(HandleObject obj,
+ JSContext* cx,
+ bool stopAtWindowProxy) {
+ MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());
+ MOZ_ASSERT(CurrentThreadCanAccessRuntime(obj->runtimeFromAnyThread()));
+ // We should know who's asking.
+ MOZ_ASSERT(cx);
+ MOZ_ASSERT(cx->realm());
+
+ if (!obj->is<WrapperObject>() ||
+ MOZ_UNLIKELY(stopAtWindowProxy && IsWindowProxy(obj))) {
+ return obj;
+ }
+
+ const Wrapper* handler = Wrapper::wrapperHandler(obj);
+ if (!handler->hasSecurityPolicy() ||
+ handler->dynamicCheckedUnwrapAllowed(obj, cx)) {
+ return Wrapper::wrappedObject(obj);
+ }
+
+ return nullptr;
+}
+
+void js::ReportAccessDenied(JSContext* cx) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_OBJECT_ACCESS_DENIED);
+}
+
+const char Wrapper::family = 0;
+const Wrapper Wrapper::singleton((unsigned)0);
+const Wrapper Wrapper::singletonWithPrototype((unsigned)0, true);
+JSObject* const Wrapper::defaultProto = TaggedProto::LazyProto;
+
+/* Compartments. */
+
+JSObject* js::TransparentObjectWrapper(JSContext* cx, HandleObject existing,
+ HandleObject obj) {
+ // Allow wrapping outer window proxies.
+ MOZ_ASSERT(!obj->is<WrapperObject>() || IsWindowProxy(obj));
+ return Wrapper::New(cx, obj, &CrossCompartmentWrapper::singleton);
+}
+
+ErrorCopier::~ErrorCopier() {
+ JSContext* cx = ar->context();
+
+ // The provenance of Debugger.DebuggeeWouldRun is the topmost locking
+ // debugger compartment; it should not be copied around.
+ if (ar->origin()->compartment() != cx->compartment() &&
+ cx->isExceptionPending() && !cx->isThrowingDebuggeeWouldRun()) {
+ RootedValue exc(cx);
+ if (cx->getPendingException(&exc) && exc.isObject() &&
+ exc.toObject().is<ErrorObject>()) {
+ RootedSavedFrame stack(cx, cx->getPendingExceptionStack());
+ cx->clearPendingException();
+ ar.reset();
+ Rooted<ErrorObject*> errObj(cx, &exc.toObject().as<ErrorObject>());
+ if (JSObject* copyobj = CopyErrorObject(cx, errObj)) {
+ RootedValue rootedCopy(cx, ObjectValue(*copyobj));
+ cx->setPendingException(rootedCopy, stack);
+ }
+ }
+ }
+}