summaryrefslogtreecommitdiffstats
path: root/js/src/proxy/Wrapper.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/proxy/Wrapper.cpp')
-rw-r--r--js/src/proxy/Wrapper.cpp456
1 files changed, 456 insertions, 0 deletions
diff --git a/js/src/proxy/Wrapper.cpp b/js/src/proxy/Wrapper.cpp
new file mode 100644
index 0000000000..0c31a11d38
--- /dev/null
+++ b/js/src/proxy/Wrapper.cpp
@@ -0,0 +1,456 @@
+/* -*- 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/CallAndConstruct.h" // JS::Construct, JS::IsConstructor
+#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/Compartment.h"
+#include "vm/ErrorObject.h"
+#include "vm/Interpreter.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<mozilla::Maybe<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, JS::PropertyKey::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, JS::PropertyKey::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, JS::PropertyKey::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, JS::PropertyKey::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, JS::PropertyKey::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::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, JS::PropertyKey::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, JS::PropertyKey::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::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_PUBLIC_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_PUBLIC_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_PUBLIC_API JSObject* js::CheckedUnwrapStatic(JSObject* obj) {
+ while (true) {
+ JSObject* wrapper = obj;
+ obj = UnwrapOneCheckedStatic(obj);
+ if (!obj || obj == wrapper) {
+ return obj;
+ }
+ }
+}
+
+JS_PUBLIC_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_PUBLIC_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_PUBLIC_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>()) {
+ Rooted<SavedFrame*> 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);
+ }
+ }
+ }
+}