/* -*- 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 desc) const { assertEnteredPolicy(cx, proxy, id, GET | SET | GET_PROPERTY_DESCRIPTOR); RootedObject target(cx, proxy->as().target()); return GetOwnPropertyDescriptor(cx, target, id, desc); } bool ForwardingProxyHandler::defineProperty(JSContext* cx, HandleObject proxy, HandleId id, Handle desc, ObjectOpResult& result) const { assertEnteredPolicy(cx, proxy, id, SET); RootedObject target(cx, proxy->as().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().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().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().target()); return EnumerateProperties(cx, target, props); } bool ForwardingProxyHandler::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const { RootedObject target(cx, proxy->as().target()); return GetPrototype(cx, target, protop); } bool ForwardingProxyHandler::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, ObjectOpResult& result) const { RootedObject target(cx, proxy->as().target()); return SetPrototype(cx, target, proto, result); } bool ForwardingProxyHandler::getPrototypeIfOrdinary( JSContext* cx, HandleObject proxy, bool* isOrdinary, MutableHandleObject protop) const { RootedObject target(cx, proxy->as().target()); return GetPrototypeIfOrdinary(cx, target, isOrdinary, protop); } bool ForwardingProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const { RootedObject target(cx, proxy->as().target()); return SetImmutablePrototype(cx, target, succeeded); } bool ForwardingProxyHandler::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) const { RootedObject target(cx, proxy->as().target()); return PreventExtensions(cx, target, result); } bool ForwardingProxyHandler::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const { RootedObject target(cx, proxy->as().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().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().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().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().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().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().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().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().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().target()); return HasInstance(cx, target, v, bp); } bool ForwardingProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const { RootedObject target(cx, proxy->as().target()); return JS::GetBuiltinClass(cx, target, cls); } bool ForwardingProxyHandler::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const { RootedObject target(cx, proxy->as().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().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().target()); return fun_toStringHelper(cx, target, isToSource); } RegExpShared* ForwardingProxyHandler::regexp_toShared( JSContext* cx, HandleObject proxy) const { RootedObject target(cx, proxy->as().target()); return RegExpToShared(cx, target); } bool ForwardingProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const { RootedObject target(cx, proxy->as().target()); return Unbox(cx, target, vp); } bool ForwardingProxyHandler::isCallable(JSObject* obj) const { JSObject* target = obj->as().target(); return target->isCallable(); } bool ForwardingProxyHandler::isConstructor(JSObject* obj) const { JSObject* target = obj->as().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 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 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().renew(handler, ObjectValue(*obj)); return existing; } JSObject* Wrapper::wrappedObject(JSObject* wrapper) { MOZ_ASSERT(wrapper->is()); JSObject* target = wrapper->as().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() || MOZ_UNLIKELY(IsWindowProxy(wrapped))) { break; } wrapped = wrapped->as().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() || 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() || 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() || 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() || 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()) { RootedSavedFrame stack(cx, cx->getPendingExceptionStack()); cx->clearPendingException(); ar.reset(); Rooted errObj(cx, &exc.toObject().as()); if (JSObject* copyobj = CopyErrorObject(cx, errObj)) { RootedValue rootedCopy(cx, ObjectValue(*copyobj)); cx->setPendingException(rootedCopy, stack); } } } }