diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/proxy/Wrapper.cpp | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | js/src/proxy/Wrapper.cpp | 473 |
1 files changed, 473 insertions, 0 deletions
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); + } + } + } +} |