summaryrefslogtreecommitdiffstats
path: root/js/src/proxy/BaseProxyHandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/proxy/BaseProxyHandler.cpp')
-rw-r--r--js/src/proxy/BaseProxyHandler.cpp426
1 files changed, 426 insertions, 0 deletions
diff --git a/js/src/proxy/BaseProxyHandler.cpp b/js/src/proxy/BaseProxyHandler.cpp
new file mode 100644
index 0000000000..1dc4e03664
--- /dev/null
+++ b/js/src/proxy/BaseProxyHandler.cpp
@@ -0,0 +1,426 @@
+/* -*- 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 "NamespaceImports.h"
+
+#include "gc/GC.h"
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/Proxy.h"
+#include "proxy/DeadObjectProxy.h"
+#include "vm/Interpreter.h"
+#include "vm/ProxyObject.h"
+#include "vm/WellKnownAtom.h" // js_*_str
+#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<mozilla::Maybe<PropertyDescriptor>> desc(cx);
+ if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) {
+ return false;
+ }
+ *bp = desc.isSome();
+ 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<mozilla::Maybe<PropertyDescriptor>> desc(cx);
+ if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) {
+ return false;
+ }
+ if (desc.isSome()) {
+ desc->assertComplete();
+ }
+
+ // Step 3.
+ if (desc.isNothing()) {
+ // 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->getter());
+
+ // 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<mozilla::Maybe<PropertyDescriptor>> ownDesc(cx);
+ if (!getOwnPropertyDescriptor(cx, proxy, id, &ownDesc)) {
+ return false;
+ }
+ if (ownDesc.isSome()) {
+ ownDesc->assertComplete();
+ }
+
+ // 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<mozilla::Maybe<PropertyDescriptor>> ownDesc_,
+ ObjectOpResult& result) {
+ Rooted<PropertyDescriptor> ownDesc(cx);
+
+ // Step 4.
+ if (ownDesc_.isNothing()) {
+ // 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.set(PropertyDescriptor::Data(
+ UndefinedValue(),
+ {JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Enumerable,
+ JS::PropertyAttribute::Writable}));
+ } else {
+ ownDesc.set(*ownDesc_);
+ }
+
+ // 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());
+
+ // Steps 5.c-d.
+ Rooted<mozilla::Maybe<PropertyDescriptor>> existingDescriptor(cx);
+ if (!GetOwnPropertyDescriptor(cx, receiverObj, id, &existingDescriptor)) {
+ return false;
+ }
+
+ // Step 5.e.
+ if (existingDescriptor.isSome()) {
+ // 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.
+ Rooted<PropertyDescriptor> desc(cx);
+ if (existingDescriptor.isSome()) {
+ desc = PropertyDescriptor::Empty();
+ desc.setValue(v);
+ } else {
+ desc = PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
+ JS::PropertyAttribute::Enumerable,
+ JS::PropertyAttribute::Writable});
+ }
+ return DefineProperty(cx, receiverObj, id, desc, result);
+ }
+
+ // Step 6.
+ MOZ_ASSERT(ownDesc.isAccessorDescriptor());
+ RootedObject setter(cx);
+ if (ownDesc.hasSetter()) {
+ setter = ownDesc.setter();
+ }
+ 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, JS::PropertyKey::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 (id.isSymbol()) {
+ continue;
+ }
+
+ AutoWaivePolicy policy(cx, proxy, id, BaseProxyHandler::GET);
+ Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
+ if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) {
+ return false;
+ }
+ if (desc.isSome()) {
+ desc->assertComplete();
+ }
+
+ if (desc.isSome() && 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, JS::PropertyKey::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::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(JS::GCContext* gcx, 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, JS::PropertyKey::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_PUBLIC_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->gcContext(), proxy);
+
+ proxy->as<ProxyObject>().nuke();
+
+ MOZ_ASSERT(IsDeadProxyObject(proxy));
+}
+
+JS_PUBLIC_API void js::NukeRemovedCrossCompartmentWrapper(JSContext* cx,
+ JSObject* wrapper) {
+ MOZ_ASSERT(wrapper->is<CrossCompartmentWrapperObject>());
+
+ NotifyGCNukeWrapper(cx, 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));
+}