michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "jswrapper.h" michael@0: michael@0: #include "jscntxt.h" michael@0: #include "jscompartment.h" michael@0: #include "jsexn.h" michael@0: #include "jsgc.h" michael@0: #include "jsiter.h" michael@0: michael@0: #include "vm/ErrorObject.h" michael@0: #include "vm/WrapperObject.h" michael@0: michael@0: #include "jsobjinlines.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: michael@0: const char js::sWrapperFamily = 0; michael@0: michael@0: /* michael@0: * Wrapper forwards this call directly to the wrapped object for efficiency michael@0: * and transparency. In particular, the hint is needed to properly stringify michael@0: * Date objects in certain cases - see bug 646129. Note also the michael@0: * SecurityWrapper overrides this trap to avoid information leaks. See bug michael@0: * 720619. michael@0: */ michael@0: bool michael@0: Wrapper::defaultValue(JSContext *cx, HandleObject proxy, JSType hint, MutableHandleValue vp) michael@0: { michael@0: vp.set(ObjectValue(*proxy->as().target())); michael@0: if (hint == JSTYPE_VOID) michael@0: return ToPrimitive(cx, vp); michael@0: return ToPrimitive(cx, hint, vp); michael@0: } michael@0: michael@0: JSObject * michael@0: Wrapper::New(JSContext *cx, JSObject *obj, JSObject *parent, Wrapper *handler, michael@0: const WrapperOptions *options) michael@0: { michael@0: JS_ASSERT(parent); michael@0: michael@0: AutoMarkInDeadZone amd(cx->zone()); michael@0: michael@0: RootedValue priv(cx, ObjectValue(*obj)); michael@0: mozilla::Maybe opts; michael@0: if (!options) { michael@0: opts.construct(); michael@0: opts.ref().selectDefaultClass(obj->isCallable()); michael@0: options = opts.addr(); michael@0: } michael@0: return NewProxyObject(cx, handler, priv, options->proto(), parent, *options); michael@0: } michael@0: michael@0: JSObject * michael@0: Wrapper::Renew(JSContext *cx, JSObject *existing, JSObject *obj, Wrapper *handler) michael@0: { michael@0: JS_ASSERT(!obj->isCallable()); michael@0: existing->as().renew(cx, handler, ObjectValue(*obj)); michael@0: return existing; michael@0: } michael@0: michael@0: Wrapper * michael@0: Wrapper::wrapperHandler(JSObject *wrapper) michael@0: { michael@0: JS_ASSERT(wrapper->is()); michael@0: return static_cast(wrapper->as().handler()); michael@0: } michael@0: michael@0: JSObject * michael@0: Wrapper::wrappedObject(JSObject *wrapper) michael@0: { michael@0: JS_ASSERT(wrapper->is()); michael@0: return wrapper->as().target(); michael@0: } michael@0: michael@0: JS_FRIEND_API(JSObject *) michael@0: js::UncheckedUnwrap(JSObject *wrapped, bool stopAtOuter, unsigned *flagsp) michael@0: { michael@0: unsigned flags = 0; michael@0: while (true) { michael@0: if (!wrapped->is() || michael@0: MOZ_UNLIKELY(stopAtOuter && wrapped->getClass()->ext.innerObject)) michael@0: { michael@0: break; michael@0: } michael@0: flags |= Wrapper::wrapperHandler(wrapped)->flags(); michael@0: wrapped = wrapped->as().private_().toObjectOrNull(); michael@0: } michael@0: if (flagsp) michael@0: *flagsp = flags; michael@0: return wrapped; michael@0: } michael@0: michael@0: JS_FRIEND_API(JSObject *) michael@0: js::CheckedUnwrap(JSObject *obj, bool stopAtOuter) michael@0: { michael@0: while (true) { michael@0: JSObject *wrapper = obj; michael@0: obj = UnwrapOneChecked(obj, stopAtOuter); michael@0: if (!obj || obj == wrapper) michael@0: return obj; michael@0: } michael@0: } michael@0: michael@0: JS_FRIEND_API(JSObject *) michael@0: js::UnwrapOneChecked(JSObject *obj, bool stopAtOuter) michael@0: { michael@0: if (!obj->is() || michael@0: MOZ_UNLIKELY(!!obj->getClass()->ext.innerObject && stopAtOuter)) michael@0: { michael@0: return obj; michael@0: } michael@0: michael@0: Wrapper *handler = Wrapper::wrapperHandler(obj); michael@0: return handler->hasSecurityPolicy() ? nullptr : Wrapper::wrappedObject(obj); michael@0: } michael@0: michael@0: bool michael@0: js::IsCrossCompartmentWrapper(JSObject *obj) michael@0: { michael@0: return IsWrapper(obj) && michael@0: !!(Wrapper::wrapperHandler(obj)->flags() & Wrapper::CROSS_COMPARTMENT); michael@0: } michael@0: michael@0: Wrapper::Wrapper(unsigned flags, bool hasPrototype) : DirectProxyHandler(&sWrapperFamily) michael@0: , mFlags(flags) michael@0: { michael@0: setHasPrototype(hasPrototype); michael@0: } michael@0: michael@0: Wrapper::~Wrapper() michael@0: { michael@0: } michael@0: michael@0: Wrapper Wrapper::singleton((unsigned)0); michael@0: Wrapper Wrapper::singletonWithPrototype((unsigned)0, true); michael@0: JSObject *Wrapper::defaultProto = TaggedProto::LazyProto; michael@0: michael@0: /* Compartments. */ michael@0: michael@0: extern JSObject * michael@0: js::TransparentObjectWrapper(JSContext *cx, HandleObject existing, HandleObject obj, michael@0: HandleObject wrappedProto, HandleObject parent, michael@0: unsigned flags) michael@0: { michael@0: // Allow wrapping outer window proxies. michael@0: JS_ASSERT(!obj->is() || obj->getClass()->ext.innerObject); michael@0: JS_ASSERT(wrappedProto == TaggedProto::LazyProto); michael@0: return Wrapper::New(cx, obj, parent, &CrossCompartmentWrapper::singleton); michael@0: } michael@0: michael@0: ErrorCopier::~ErrorCopier() michael@0: { michael@0: JSContext *cx = ac.ref().context()->asJSContext(); michael@0: if (ac.ref().origin() != cx->compartment() && cx->isExceptionPending()) { michael@0: RootedValue exc(cx); michael@0: if (cx->getPendingException(&exc) && exc.isObject() && exc.toObject().is()) { michael@0: cx->clearPendingException(); michael@0: ac.destroy(); michael@0: Rooted errObj(cx, &exc.toObject().as()); michael@0: JSObject *copyobj = js_CopyErrorObject(cx, errObj, scope); michael@0: if (copyobj) michael@0: cx->setPendingException(ObjectValue(*copyobj)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Cross compartment wrappers. */ michael@0: michael@0: CrossCompartmentWrapper::CrossCompartmentWrapper(unsigned flags, bool hasPrototype) michael@0: : Wrapper(CROSS_COMPARTMENT | flags, hasPrototype) michael@0: { michael@0: } michael@0: michael@0: CrossCompartmentWrapper::~CrossCompartmentWrapper() michael@0: { michael@0: } michael@0: michael@0: bool Wrapper::finalizeInBackground(Value priv) michael@0: { michael@0: if (!priv.isObject()) michael@0: return true; michael@0: michael@0: /* michael@0: * Make the 'background-finalized-ness' of the wrapper the same as the michael@0: * wrapped object, to allow transplanting between them. michael@0: * michael@0: * If the wrapped object is in the nursery then we know it doesn't have a michael@0: * finalizer, and so background finalization is ok. michael@0: */ michael@0: if (IsInsideNursery(priv.toObject().runtimeFromMainThread(), &priv.toObject())) michael@0: return true; michael@0: return IsBackgroundFinalized(priv.toObject().tenuredGetAllocKind()); michael@0: } michael@0: michael@0: #define PIERCE(cx, wrapper, pre, op, post) \ michael@0: JS_BEGIN_MACRO \ michael@0: bool ok; \ michael@0: { \ michael@0: AutoCompartment call(cx, wrappedObject(wrapper)); \ michael@0: ok = (pre) && (op); \ michael@0: } \ michael@0: return ok && (post); \ michael@0: JS_END_MACRO michael@0: michael@0: #define NOTHING (true) michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::isExtensible(JSContext *cx, HandleObject wrapper, bool *extensible) michael@0: { michael@0: PIERCE(cx, wrapper, michael@0: NOTHING, michael@0: Wrapper::isExtensible(cx, wrapper, extensible), michael@0: NOTHING); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::preventExtensions(JSContext *cx, HandleObject wrapper) michael@0: { michael@0: PIERCE(cx, wrapper, michael@0: NOTHING, michael@0: Wrapper::preventExtensions(cx, wrapper), michael@0: NOTHING); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::getPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: RootedId idCopy(cx, id); michael@0: PIERCE(cx, wrapper, michael@0: cx->compartment()->wrapId(cx, idCopy.address()), michael@0: Wrapper::getPropertyDescriptor(cx, wrapper, idCopy, desc), michael@0: cx->compartment()->wrap(cx, desc)); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::getOwnPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: RootedId idCopy(cx, id); michael@0: PIERCE(cx, wrapper, michael@0: cx->compartment()->wrapId(cx, idCopy.address()), michael@0: Wrapper::getOwnPropertyDescriptor(cx, wrapper, idCopy, desc), michael@0: cx->compartment()->wrap(cx, desc)); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::defineProperty(JSContext *cx, HandleObject wrapper, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: RootedId idCopy(cx, id); michael@0: Rooted desc2(cx, desc); michael@0: PIERCE(cx, wrapper, michael@0: cx->compartment()->wrapId(cx, idCopy.address()) && cx->compartment()->wrap(cx, &desc2), michael@0: Wrapper::defineProperty(cx, wrapper, idCopy, &desc2), michael@0: NOTHING); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::getOwnPropertyNames(JSContext *cx, HandleObject wrapper, michael@0: AutoIdVector &props) michael@0: { michael@0: PIERCE(cx, wrapper, michael@0: NOTHING, michael@0: Wrapper::getOwnPropertyNames(cx, wrapper, props), michael@0: cx->compartment()->wrap(cx, props)); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::delete_(JSContext *cx, HandleObject wrapper, HandleId id, bool *bp) michael@0: { michael@0: RootedId idCopy(cx, id); michael@0: PIERCE(cx, wrapper, michael@0: cx->compartment()->wrapId(cx, idCopy.address()), michael@0: Wrapper::delete_(cx, wrapper, idCopy, bp), michael@0: NOTHING); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::enumerate(JSContext *cx, HandleObject wrapper, AutoIdVector &props) michael@0: { michael@0: PIERCE(cx, wrapper, michael@0: NOTHING, michael@0: Wrapper::enumerate(cx, wrapper, props), michael@0: cx->compartment()->wrap(cx, props)); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::has(JSContext *cx, HandleObject wrapper, HandleId id, bool *bp) michael@0: { michael@0: RootedId idCopy(cx, id); michael@0: PIERCE(cx, wrapper, michael@0: cx->compartment()->wrapId(cx, idCopy.address()), michael@0: Wrapper::has(cx, wrapper, idCopy, bp), michael@0: NOTHING); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::hasOwn(JSContext *cx, HandleObject wrapper, HandleId id, bool *bp) michael@0: { michael@0: RootedId idCopy(cx, id); michael@0: PIERCE(cx, wrapper, michael@0: cx->compartment()->wrapId(cx, idCopy.address()), michael@0: Wrapper::hasOwn(cx, wrapper, idCopy, bp), michael@0: NOTHING); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::get(JSContext *cx, HandleObject wrapper, HandleObject receiver, michael@0: HandleId id, MutableHandleValue vp) michael@0: { michael@0: RootedObject receiverCopy(cx, receiver); michael@0: RootedId idCopy(cx, id); michael@0: { michael@0: AutoCompartment call(cx, wrappedObject(wrapper)); michael@0: if (!cx->compartment()->wrap(cx, &receiverCopy) || michael@0: !cx->compartment()->wrapId(cx, idCopy.address())) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: if (!Wrapper::get(cx, wrapper, receiverCopy, idCopy, vp)) michael@0: return false; michael@0: } michael@0: return cx->compartment()->wrap(cx, vp); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::set(JSContext *cx, HandleObject wrapper, HandleObject receiver, michael@0: HandleId id, bool strict, MutableHandleValue vp) michael@0: { michael@0: RootedObject receiverCopy(cx, receiver); michael@0: RootedId idCopy(cx, id); michael@0: PIERCE(cx, wrapper, michael@0: cx->compartment()->wrap(cx, &receiverCopy) && michael@0: cx->compartment()->wrapId(cx, idCopy.address()) && michael@0: cx->compartment()->wrap(cx, vp), michael@0: Wrapper::set(cx, wrapper, receiverCopy, idCopy, strict, vp), michael@0: NOTHING); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::keys(JSContext *cx, HandleObject wrapper, AutoIdVector &props) michael@0: { michael@0: PIERCE(cx, wrapper, michael@0: NOTHING, michael@0: Wrapper::keys(cx, wrapper, props), michael@0: cx->compartment()->wrap(cx, props)); michael@0: } michael@0: michael@0: /* michael@0: * We can reify non-escaping iterator objects instead of having to wrap them. This michael@0: * allows fast iteration over objects across a compartment boundary. michael@0: */ michael@0: static bool michael@0: CanReify(HandleValue vp) michael@0: { michael@0: JSObject *obj; michael@0: return vp.isObject() && michael@0: (obj = &vp.toObject())->is() && michael@0: (obj->as().getNativeIterator()->flags & JSITER_ENUMERATE); michael@0: } michael@0: michael@0: struct AutoCloseIterator michael@0: { michael@0: AutoCloseIterator(JSContext *cx, JSObject *obj) : cx(cx), obj(cx, obj) {} michael@0: michael@0: ~AutoCloseIterator() { if (obj) CloseIterator(cx, obj); } michael@0: michael@0: void clear() { obj = nullptr; } michael@0: michael@0: private: michael@0: JSContext *cx; michael@0: RootedObject obj; michael@0: }; michael@0: michael@0: static bool michael@0: Reify(JSContext *cx, JSCompartment *origin, MutableHandleValue vp) michael@0: { michael@0: Rooted iterObj(cx, &vp.toObject().as()); michael@0: NativeIterator *ni = iterObj->getNativeIterator(); michael@0: michael@0: AutoCloseIterator close(cx, iterObj); michael@0: michael@0: /* Wrap the iteratee. */ michael@0: RootedObject obj(cx, ni->obj); michael@0: if (!origin->wrap(cx, &obj)) michael@0: return false; michael@0: michael@0: /* michael@0: * Wrap the elements in the iterator's snapshot. michael@0: * N.B. the order of closing/creating iterators is important due to the michael@0: * implicit cx->enumerators state. michael@0: */ michael@0: size_t length = ni->numKeys(); michael@0: bool isKeyIter = ni->isKeyIter(); michael@0: AutoIdVector keys(cx); michael@0: if (length > 0) { michael@0: if (!keys.reserve(length)) michael@0: return false; michael@0: for (size_t i = 0; i < length; ++i) { michael@0: RootedId id(cx); michael@0: RootedValue v(cx, StringValue(ni->begin()[i])); michael@0: if (!ValueToId(cx, v, &id)) michael@0: return false; michael@0: keys.infallibleAppend(id); michael@0: if (!origin->wrapId(cx, &keys[i])) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: close.clear(); michael@0: if (!CloseIterator(cx, iterObj)) michael@0: return false; michael@0: michael@0: if (isKeyIter) { michael@0: if (!VectorToKeyIterator(cx, obj, ni->flags, keys, vp)) michael@0: return false; michael@0: } else { michael@0: if (!VectorToValueIterator(cx, obj, ni->flags, keys, vp)) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::iterate(JSContext *cx, HandleObject wrapper, unsigned flags, michael@0: MutableHandleValue vp) michael@0: { michael@0: { michael@0: AutoCompartment call(cx, wrappedObject(wrapper)); michael@0: if (!Wrapper::iterate(cx, wrapper, flags, vp)) michael@0: return false; michael@0: } michael@0: michael@0: if (CanReify(vp)) michael@0: return Reify(cx, cx->compartment(), vp); michael@0: return cx->compartment()->wrap(cx, vp); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::call(JSContext *cx, HandleObject wrapper, const CallArgs &args) michael@0: { michael@0: RootedObject wrapped(cx, wrappedObject(wrapper)); michael@0: michael@0: { michael@0: AutoCompartment call(cx, wrapped); michael@0: michael@0: args.setCallee(ObjectValue(*wrapped)); michael@0: if (!cx->compartment()->wrap(cx, args.mutableThisv())) michael@0: return false; michael@0: michael@0: for (size_t n = 0; n < args.length(); ++n) { michael@0: if (!cx->compartment()->wrap(cx, args[n])) michael@0: return false; michael@0: } michael@0: michael@0: if (!Wrapper::call(cx, wrapper, args)) michael@0: return false; michael@0: } michael@0: michael@0: return cx->compartment()->wrap(cx, args.rval()); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::construct(JSContext *cx, HandleObject wrapper, const CallArgs &args) michael@0: { michael@0: RootedObject wrapped(cx, wrappedObject(wrapper)); michael@0: { michael@0: AutoCompartment call(cx, wrapped); michael@0: michael@0: for (size_t n = 0; n < args.length(); ++n) { michael@0: if (!cx->compartment()->wrap(cx, args[n])) michael@0: return false; michael@0: } michael@0: if (!Wrapper::construct(cx, wrapper, args)) michael@0: return false; michael@0: } michael@0: return cx->compartment()->wrap(cx, args.rval()); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, michael@0: CallArgs srcArgs) michael@0: { michael@0: RootedObject wrapper(cx, &srcArgs.thisv().toObject()); michael@0: JS_ASSERT(srcArgs.thisv().isMagic(JS_IS_CONSTRUCTING) || michael@0: !UncheckedUnwrap(wrapper)->is()); michael@0: michael@0: RootedObject wrapped(cx, wrappedObject(wrapper)); michael@0: { michael@0: AutoCompartment call(cx, wrapped); michael@0: InvokeArgs dstArgs(cx); michael@0: if (!dstArgs.init(srcArgs.length())) michael@0: return false; michael@0: michael@0: Value *src = srcArgs.base(); michael@0: Value *srcend = srcArgs.array() + srcArgs.length(); michael@0: Value *dst = dstArgs.base(); michael@0: michael@0: RootedValue source(cx); michael@0: for (; src < srcend; ++src, ++dst) { michael@0: source = *src; michael@0: if (!cx->compartment()->wrap(cx, &source)) michael@0: return false; michael@0: *dst = source.get(); michael@0: michael@0: // Handle |this| specially. When we rewrap on the other side of the michael@0: // membrane, we might apply a same-compartment security wrapper that michael@0: // will stymie this whole process. If that happens, unwrap the wrapper. michael@0: // This logic can go away when same-compartment security wrappers go away. michael@0: if ((src == srcArgs.base() + 1) && dst->isObject()) { michael@0: RootedObject thisObj(cx, &dst->toObject()); michael@0: if (thisObj->is() && michael@0: Wrapper::wrapperHandler(thisObj)->hasSecurityPolicy()) michael@0: { michael@0: JS_ASSERT(!thisObj->is()); michael@0: *dst = ObjectValue(*Wrapper::wrappedObject(thisObj)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!CallNonGenericMethod(cx, test, impl, dstArgs)) michael@0: return false; michael@0: michael@0: srcArgs.rval().set(dstArgs.rval()); michael@0: } michael@0: return cx->compartment()->wrap(cx, srcArgs.rval()); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::hasInstance(JSContext *cx, HandleObject wrapper, MutableHandleValue v, michael@0: bool *bp) michael@0: { michael@0: AutoCompartment call(cx, wrappedObject(wrapper)); michael@0: if (!cx->compartment()->wrap(cx, v)) michael@0: return false; michael@0: return Wrapper::hasInstance(cx, wrapper, v, bp); michael@0: } michael@0: michael@0: const char * michael@0: CrossCompartmentWrapper::className(JSContext *cx, HandleObject wrapper) michael@0: { michael@0: AutoCompartment call(cx, wrappedObject(wrapper)); michael@0: return Wrapper::className(cx, wrapper); michael@0: } michael@0: michael@0: JSString * michael@0: CrossCompartmentWrapper::fun_toString(JSContext *cx, HandleObject wrapper, unsigned indent) michael@0: { michael@0: RootedString str(cx); michael@0: { michael@0: AutoCompartment call(cx, wrappedObject(wrapper)); michael@0: str = Wrapper::fun_toString(cx, wrapper, indent); michael@0: if (!str) michael@0: return nullptr; michael@0: } michael@0: if (!cx->compartment()->wrap(cx, str.address())) michael@0: return nullptr; michael@0: return str; michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::regexp_toShared(JSContext *cx, HandleObject wrapper, RegExpGuard *g) michael@0: { michael@0: AutoCompartment call(cx, wrappedObject(wrapper)); michael@0: return Wrapper::regexp_toShared(cx, wrapper, g); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::defaultValue(JSContext *cx, HandleObject wrapper, JSType hint, michael@0: MutableHandleValue vp) michael@0: { michael@0: PIERCE(cx, wrapper, michael@0: NOTHING, michael@0: Wrapper::defaultValue(cx, wrapper, hint, vp), michael@0: cx->compartment()->wrap(cx, vp)); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::getPrototypeOf(JSContext *cx, HandleObject wrapper, michael@0: MutableHandleObject protop) michael@0: { michael@0: { michael@0: RootedObject wrapped(cx, wrappedObject(wrapper)); michael@0: AutoCompartment call(cx, wrapped); michael@0: if (!JSObject::getProto(cx, wrapped, protop)) michael@0: return false; michael@0: if (protop) michael@0: protop->setDelegate(cx); michael@0: } michael@0: michael@0: return cx->compartment()->wrap(cx, protop); michael@0: } michael@0: michael@0: bool michael@0: CrossCompartmentWrapper::setPrototypeOf(JSContext *cx, HandleObject wrapper, michael@0: HandleObject proto, bool *bp) michael@0: { michael@0: RootedObject protoCopy(cx, proto); michael@0: PIERCE(cx, wrapper, michael@0: cx->compartment()->wrap(cx, &protoCopy), michael@0: Wrapper::setPrototypeOf(cx, wrapper, protoCopy, bp), michael@0: NOTHING); michael@0: } michael@0: michael@0: CrossCompartmentWrapper CrossCompartmentWrapper::singleton(0u); michael@0: michael@0: /* Security wrappers. */ michael@0: michael@0: template michael@0: SecurityWrapper::SecurityWrapper(unsigned flags) michael@0: : Base(flags) michael@0: { michael@0: BaseProxyHandler::setHasSecurityPolicy(true); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: SecurityWrapper::isExtensible(JSContext *cx, HandleObject wrapper, bool *extensible) michael@0: { michael@0: // Just like BaseProxyHandler, SecurityWrappers claim by default to always michael@0: // be extensible, so as not to leak information about the state of the michael@0: // underlying wrapped thing. michael@0: *extensible = true; michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: SecurityWrapper::preventExtensions(JSContext *cx, HandleObject wrapper) michael@0: { michael@0: // See above. michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); michael@0: return false; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: SecurityWrapper::enter(JSContext *cx, HandleObject wrapper, HandleId id, michael@0: Wrapper::Action act, bool *bp) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); michael@0: *bp = false; michael@0: return false; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: SecurityWrapper::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, michael@0: CallArgs args) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); michael@0: return false; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: SecurityWrapper::setPrototypeOf(JSContext *cx, HandleObject wrapper, michael@0: HandleObject proto, bool *bp) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); michael@0: return false; michael@0: } michael@0: michael@0: // For security wrappers, we run the DefaultValue algorithm on the wrapper michael@0: // itself, which means that the existing security policy on operations like michael@0: // toString() will take effect and do the right thing here. michael@0: template michael@0: bool michael@0: SecurityWrapper::defaultValue(JSContext *cx, HandleObject wrapper, michael@0: JSType hint, MutableHandleValue vp) michael@0: { michael@0: return DefaultValue(cx, wrapper, hint, vp); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: SecurityWrapper::objectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: SecurityWrapper::regexp_toShared(JSContext *cx, HandleObject obj, RegExpGuard *g) michael@0: { michael@0: return Base::regexp_toShared(cx, obj, g); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: SecurityWrapper::defineProperty(JSContext *cx, HandleObject wrapper, michael@0: HandleId id, MutableHandle desc) michael@0: { michael@0: if (desc.getter() || desc.setter()) { michael@0: JSString *str = IdToString(cx, id); michael@0: const jschar *prop = str ? str->getCharsZ(cx) : nullptr; michael@0: JS_ReportErrorNumberUC(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_ACCESSOR_DEF_DENIED, prop); michael@0: return false; michael@0: } michael@0: michael@0: return Base::defineProperty(cx, wrapper, id, desc); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: SecurityWrapper::watch(JSContext *cx, HandleObject proxy, michael@0: HandleId id, HandleObject callable) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); michael@0: return false; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: SecurityWrapper::unwatch(JSContext *cx, HandleObject proxy, michael@0: HandleId id) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); michael@0: return false; michael@0: } michael@0: michael@0: michael@0: template class js::SecurityWrapper; michael@0: template class js::SecurityWrapper; michael@0: michael@0: DeadObjectProxy::DeadObjectProxy() michael@0: : BaseProxyHandler(&sDeadObjectFamily) michael@0: { michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) michael@0: { michael@0: // This is kind of meaningless, but dead-object semantics aside, michael@0: // [[Extensible]] always being true is consistent with other proxy types. michael@0: *extensible = true; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::preventExtensions(JSContext *cx, HandleObject proxy) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::getPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::getOwnPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::defineProperty(JSContext *cx, HandleObject wrapper, HandleId id, michael@0: MutableHandle desc) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::getOwnPropertyNames(JSContext *cx, HandleObject wrapper, michael@0: AutoIdVector &props) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::delete_(JSContext *cx, HandleObject wrapper, HandleId id, bool *bp) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::enumerate(JSContext *cx, HandleObject wrapper, AutoIdVector &props) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::call(JSContext *cx, HandleObject wrapper, const CallArgs &args) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::construct(JSContext *cx, HandleObject wrapper, const CallArgs &args) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::objectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: const char * michael@0: DeadObjectProxy::className(JSContext *cx, HandleObject wrapper) michael@0: { michael@0: return "DeadObject"; michael@0: } michael@0: michael@0: JSString * michael@0: DeadObjectProxy::fun_toString(JSContext *cx, HandleObject proxy, unsigned indent) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::defaultValue(JSContext *cx, HandleObject obj, JSType hint, MutableHandleValue vp) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: DeadObjectProxy::getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop) michael@0: { michael@0: protop.set(nullptr); michael@0: return true; michael@0: } michael@0: michael@0: DeadObjectProxy DeadObjectProxy::singleton; michael@0: const char DeadObjectProxy::sDeadObjectFamily = 0; michael@0: michael@0: bool michael@0: js::IsDeadProxyObject(JSObject *obj) michael@0: { michael@0: return obj->is() && michael@0: obj->as().handler() == &DeadObjectProxy::singleton; michael@0: } michael@0: michael@0: void michael@0: js::NukeCrossCompartmentWrapper(JSContext *cx, JSObject *wrapper) michael@0: { michael@0: JS_ASSERT(wrapper->is()); michael@0: michael@0: NotifyGCNukeWrapper(wrapper); michael@0: michael@0: wrapper->as().nuke(&DeadObjectProxy::singleton); michael@0: michael@0: JS_ASSERT(IsDeadProxyObject(wrapper)); michael@0: } michael@0: michael@0: /* michael@0: * NukeChromeCrossCompartmentWrappersForGlobal reaches into chrome and cuts michael@0: * all of the cross-compartment wrappers that point to objects parented to michael@0: * obj's global. The snag here is that we need to avoid cutting wrappers that michael@0: * point to the window object on page navigation (inner window destruction) michael@0: * and only do that on tab close (outer window destruction). Thus the michael@0: * option of how to handle the global object. michael@0: */ michael@0: JS_FRIEND_API(bool) michael@0: js::NukeCrossCompartmentWrappers(JSContext* cx, michael@0: const CompartmentFilter& sourceFilter, michael@0: const CompartmentFilter& targetFilter, michael@0: js::NukeReferencesToWindow nukeReferencesToWindow) michael@0: { michael@0: CHECK_REQUEST(cx); michael@0: JSRuntime *rt = cx->runtime(); michael@0: michael@0: // Iterate through scopes looking for system cross compartment wrappers michael@0: // that point to an object that shares a global with obj. michael@0: michael@0: for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { michael@0: if (!sourceFilter.match(c)) michael@0: continue; michael@0: michael@0: // Iterate the wrappers looking for anything interesting. michael@0: for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { michael@0: // Some cross-compartment wrappers are for strings. We're not michael@0: // interested in those. michael@0: const CrossCompartmentKey &k = e.front().key(); michael@0: if (k.kind != CrossCompartmentKey::ObjectWrapper) michael@0: continue; michael@0: michael@0: AutoWrapperRooter wobj(cx, WrapperValue(e)); michael@0: JSObject *wrapped = UncheckedUnwrap(wobj); michael@0: michael@0: if (nukeReferencesToWindow == DontNukeWindowReferences && michael@0: wrapped->getClass()->ext.innerObject) michael@0: continue; michael@0: michael@0: if (targetFilter.match(wrapped->compartment())) { michael@0: // We found a wrapper to nuke. michael@0: e.removeFront(); michael@0: NukeCrossCompartmentWrapper(cx, wobj); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Given a cross-compartment wrapper |wobj|, update it to point to michael@0: // |newTarget|. This recomputes the wrapper with JS_WrapValue, and thus can be michael@0: // useful even if wrapper already points to newTarget. michael@0: bool michael@0: js::RemapWrapper(JSContext *cx, JSObject *wobjArg, JSObject *newTargetArg) michael@0: { michael@0: RootedObject wobj(cx, wobjArg); michael@0: RootedObject newTarget(cx, newTargetArg); michael@0: JS_ASSERT(wobj->is()); michael@0: JS_ASSERT(!newTarget->is()); michael@0: JSObject *origTarget = Wrapper::wrappedObject(wobj); michael@0: JS_ASSERT(origTarget); michael@0: Value origv = ObjectValue(*origTarget); michael@0: JSCompartment *wcompartment = wobj->compartment(); michael@0: michael@0: AutoDisableProxyCheck adpc(cx->runtime()); michael@0: michael@0: // If we're mapping to a different target (as opposed to just recomputing michael@0: // for the same target), we must not have an existing wrapper for the new michael@0: // target, otherwise this will break. michael@0: JS_ASSERT_IF(origTarget != newTarget, michael@0: !wcompartment->lookupWrapper(ObjectValue(*newTarget))); michael@0: michael@0: // The old value should still be in the cross-compartment wrapper map, and michael@0: // the lookup should return wobj. michael@0: WrapperMap::Ptr p = wcompartment->lookupWrapper(origv); michael@0: JS_ASSERT(&p->value().unsafeGet()->toObject() == wobj); michael@0: wcompartment->removeWrapper(p); michael@0: michael@0: // When we remove origv from the wrapper map, its wrapper, wobj, must michael@0: // immediately cease to be a cross-compartment wrapper. Neuter it. michael@0: NukeCrossCompartmentWrapper(cx, wobj); michael@0: michael@0: // First, we wrap it in the new compartment. We try to use the existing michael@0: // wrapper, |wobj|, since it's been nuked anyway. The wrap() function has michael@0: // the choice to reuse |wobj| or not. michael@0: RootedObject tobj(cx, newTarget); michael@0: AutoCompartment ac(cx, wobj); michael@0: if (!wcompartment->wrap(cx, &tobj, wobj)) michael@0: MOZ_CRASH(); michael@0: michael@0: // If wrap() reused |wobj|, it will have overwritten it and returned with michael@0: // |tobj == wobj|. Otherwise, |tobj| will point to a new wrapper and |wobj| michael@0: // will still be nuked. In the latter case, we replace |wobj| with the michael@0: // contents of the new wrapper in |tobj|. michael@0: if (tobj != wobj) { michael@0: // Now, because we need to maintain object identity, we do a brain michael@0: // transplant on the old object so that it contains the contents of the michael@0: // new one. michael@0: if (!JSObject::swap(cx, wobj, tobj)) michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: // Before swapping, this wrapper came out of wrap(), which enforces the michael@0: // invariant that the wrapper in the map points directly to the key. michael@0: JS_ASSERT(Wrapper::wrappedObject(wobj) == newTarget); michael@0: michael@0: // Update the entry in the compartment's wrapper map to point to the old michael@0: // wrapper, which has now been updated (via reuse or swap). michael@0: JS_ASSERT(wobj->is()); michael@0: wcompartment->putWrapper(cx, ObjectValue(*newTarget), ObjectValue(*wobj)); michael@0: return true; michael@0: } michael@0: michael@0: // Remap all cross-compartment wrappers pointing to |oldTarget| to point to michael@0: // |newTarget|. All wrappers are recomputed. michael@0: JS_FRIEND_API(bool) michael@0: js::RemapAllWrappersForObject(JSContext *cx, JSObject *oldTargetArg, michael@0: JSObject *newTargetArg) michael@0: { michael@0: RootedValue origv(cx, ObjectValue(*oldTargetArg)); michael@0: RootedObject newTarget(cx, newTargetArg); michael@0: michael@0: AutoWrapperVector toTransplant(cx); michael@0: if (!toTransplant.reserve(cx->runtime()->numCompartments)) michael@0: return false; michael@0: michael@0: for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) { michael@0: if (WrapperMap::Ptr wp = c->lookupWrapper(origv)) { michael@0: // We found a wrapper. Remember and root it. michael@0: toTransplant.infallibleAppend(WrapperValue(wp)); michael@0: } michael@0: } michael@0: michael@0: for (WrapperValue *begin = toTransplant.begin(), *end = toTransplant.end(); michael@0: begin != end; ++begin) michael@0: { michael@0: if (!RemapWrapper(cx, &begin->toObject(), newTarget)) michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: js::RecomputeWrappers(JSContext *cx, const CompartmentFilter &sourceFilter, michael@0: const CompartmentFilter &targetFilter) michael@0: { michael@0: AutoMaybeTouchDeadZones agc(cx); michael@0: michael@0: AutoWrapperVector toRecompute(cx); michael@0: michael@0: for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) { michael@0: // Filter by source compartment. michael@0: if (!sourceFilter.match(c)) michael@0: continue; michael@0: michael@0: // Iterate over the wrappers, filtering appropriately. michael@0: for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { michael@0: // Filter out non-objects. michael@0: const CrossCompartmentKey &k = e.front().key(); michael@0: if (k.kind != CrossCompartmentKey::ObjectWrapper) michael@0: continue; michael@0: michael@0: // Filter by target compartment. michael@0: if (!targetFilter.match(static_cast(k.wrapped)->compartment())) michael@0: continue; michael@0: michael@0: // Add it to the list. michael@0: if (!toRecompute.append(WrapperValue(e))) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Recompute all the wrappers in the list. michael@0: for (WrapperValue *begin = toRecompute.begin(), *end = toRecompute.end(); begin != end; ++begin) michael@0: { michael@0: JSObject *wrapper = &begin->toObject(); michael@0: JSObject *wrapped = Wrapper::wrappedObject(wrapper); michael@0: if (!RemapWrapper(cx, wrapper, wrapped)) michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: return true; michael@0: }