/* -*- 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 "vm/ProxyObject.h" #include "gc/Allocator.h" #include "gc/GCProbes.h" #include "gc/Marking.h" #include "gc/Zone.h" #include "proxy/DeadObjectProxy.h" #include "vm/Compartment.h" #include "vm/Realm.h" #include "gc/ObjectKind-inl.h" using namespace js; static gc::AllocKind GetProxyGCObjectKind(const JSClass* clasp, const BaseProxyHandler* handler, const Value& priv, bool withInlineValues) { MOZ_ASSERT(clasp->isProxyObject()); uint32_t nreserved = JSCLASS_RESERVED_SLOTS(clasp); // For now assert each Proxy Class has at least 1 reserved slot. This is // not a hard requirement, but helps catch Classes that need an explicit // JSCLASS_HAS_RESERVED_SLOTS since bug 1360523. MOZ_ASSERT(nreserved > 0); uint32_t nslots = 0; if (withInlineValues) { nslots = detail::ProxyValueArray::allocCount(nreserved); } MOZ_ASSERT(nslots <= NativeObject::MAX_FIXED_SLOTS); gc::AllocKind kind = gc::GetGCObjectKind(nslots); if (handler->finalizeInBackground(priv)) { kind = ForegroundToBackgroundAllocKind(kind); } return kind; } void ProxyObject::init(const BaseProxyHandler* handler, HandleValue priv, JSContext* cx) { setInlineValueArray(); detail::ProxyValueArray* values = detail::GetProxyDataLayout(this)->values(); values->init(numReservedSlots()); data.handler = handler; if (IsCrossCompartmentWrapper(this)) { MOZ_ASSERT(cx->global() == &cx->compartment()->globalForNewCCW()); setCrossCompartmentPrivate(priv); } else { setSameCompartmentPrivate(priv); } // The expando slot is nullptr until required by the installation of // a private field. setExpando(nullptr); } /* static */ ProxyObject* ProxyObject::New(JSContext* cx, const BaseProxyHandler* handler, HandleValue priv, TaggedProto proto_, const JSClass* clasp) { Rooted proto(cx, proto_); MOZ_ASSERT(!clasp->isNativeObject()); MOZ_ASSERT(clasp->isProxyObject()); MOZ_ASSERT(isValidProxyClass(clasp)); MOZ_ASSERT(clasp->shouldDelayMetadataBuilder()); MOZ_ASSERT_IF(proto.isObject(), cx->compartment() == proto.toObject()->compartment()); MOZ_ASSERT(clasp->hasFinalize()); #ifdef DEBUG if (priv.isGCThing()) { JS::AssertCellIsNotGray(priv.toGCThing()); } #endif gc::AllocKind allocKind = GetProxyGCObjectKind(clasp, handler, priv, /* withInlineValues = */ true); Realm* realm = cx->realm(); AutoSetNewObjectMetadata metadata(cx); // Try to look up the shape in the NewProxyCache. Rooted shape(cx); if (!realm->newProxyCache.lookup(clasp, proto, shape.address())) { shape = ProxyShape::getShape(cx, clasp, realm, proto, ObjectFlags()); if (!shape) { return nullptr; } realm->newProxyCache.add(shape); } MOZ_ASSERT(shape->realm() == realm); MOZ_ASSERT(!IsAboutToBeFinalizedUnbarriered(shape.get())); // Ensure that the wrapper has the same lifetime assumptions as the // wrappee. Prefer to allocate in the nursery, when possible. gc::Heap heap; if ((priv.isGCThing() && priv.toGCThing()->isTenured()) || !handler->canNurseryAllocate()) { heap = gc::Heap::Tenured; } else { heap = gc::Heap::Default; } debugCheckNewObject(shape, allocKind, heap); ProxyObject* proxy = cx->newCell(allocKind, heap, clasp); if (!proxy) { return nullptr; } proxy->initShape(shape); MOZ_ASSERT(clasp->shouldDelayMetadataBuilder()); realm->setObjectPendingMetadata(proxy); gc::gcprobes::CreateObject(proxy); proxy->init(handler, priv, cx); return proxy; } gc::AllocKind ProxyObject::allocKindForTenure() const { Value priv = private_(); return GetProxyGCObjectKind(getClass(), data.handler, priv, usingInlineValueArray()); } void ProxyObject::setCrossCompartmentPrivate(const Value& priv) { setPrivate(priv); } void ProxyObject::setSameCompartmentPrivate(const Value& priv) { MOZ_ASSERT(IsObjectValueInCompartment(priv, compartment())); setPrivate(priv); } inline void ProxyObject::setPrivate(const Value& priv) { #ifdef DEBUG JS::AssertValueIsNotGray(priv); #endif *slotOfPrivate() = priv; } void ProxyObject::setExpando(JSObject* expando) { // Ensure we're in the same compartment as the proxy object: Don't want the // expando to end up as a CCW. MOZ_ASSERT_IF(expando, expando->compartment() == compartment()); // Ensure that we don't accidentally end up pointing to a // grey object, which would violate GC invariants. MOZ_ASSERT_IF(!zone()->isGCPreparing() && isMarkedBlack() && expando, !JS::GCThingIsMarkedGray(JS::GCCellPtr(expando))); *slotOfExpando() = ObjectOrNullValue(expando); } void ProxyObject::nuke() { // Notify the zone that a delegate is no longer a delegate. Be careful not to // expose this pointer, because it has already been removed from the wrapper // map yet we have assertions during tracing that will verify that it is // still present. JSObject* delegate = UncheckedUnwrapWithoutExpose(this); if (delegate != this) { delegate->zone()->beforeClearDelegate(this, delegate); } // Clear the target reference and replaced it with a value that encodes // various information about the original target. setSameCompartmentPrivate(DeadProxyTargetValue(this)); // Clear out the expando setExpando(nullptr); // Update the handler to make this a DeadObjectProxy. setHandler(&DeadObjectProxy::singleton); // The proxy's reserved slots are not cleared and will continue to be // traced. This avoids the possibility of triggering write barriers while // nuking proxies in dead compartments which could otherwise cause those // compartments to be kept alive. Note that these are slots cannot hold // cross compartment pointers, so this cannot cause the target compartment // to leak. } JS_PUBLIC_API void js::detail::SetValueInProxy(Value* slot, const Value& value) { // Slots in proxies are not GCPtrs, so do a cast whenever assigning // values to them which might trigger a barrier. *reinterpret_cast*>(slot) = value; }