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