ProxyObject.cpp (6908B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "vm/ProxyObject.h" 8 9 #include "gc/GCProbes.h" 10 #include "gc/Marking.h" 11 #include "gc/Zone.h" 12 #include "proxy/DeadObjectProxy.h" 13 #include "vm/Compartment.h" 14 #include "vm/Realm.h" 15 16 #include "gc/ObjectKind-inl.h" 17 #include "vm/JSContext-inl.h" 18 19 using namespace js; 20 21 static gc::AllocKind GetProxyGCObjectKind(const JSClass* clasp, 22 const BaseProxyHandler* handler, 23 const Value& priv, 24 bool withInlineValues) { 25 MOZ_ASSERT(clasp->isProxyObject()); 26 27 uint32_t nreserved = JSCLASS_RESERVED_SLOTS(clasp); 28 29 // For now assert each Proxy Class has at least 1 reserved slot. This is 30 // not a hard requirement, but helps catch Classes that need an explicit 31 // JSCLASS_HAS_RESERVED_SLOTS since bug 1360523. 32 MOZ_ASSERT(nreserved > 0); 33 34 uint32_t nslots = 0; 35 if (withInlineValues) { 36 nslots = detail::ProxyValueArray::allocCount(nreserved); 37 } 38 39 MOZ_ASSERT(nslots <= NativeObject::MAX_FIXED_SLOTS); 40 gc::AllocKind kind = gc::GetGCObjectKind(nslots); 41 gc::FinalizeKind finalizeKind; 42 43 // Bug 1957589: Support non-finalized proxies as well. 44 if (handler->finalizeInBackground(priv)) { 45 finalizeKind = gc::FinalizeKind::Background; 46 } else { 47 finalizeKind = gc::FinalizeKind::Foreground; 48 } 49 50 return gc::GetFinalizedAllocKind(kind, finalizeKind); 51 } 52 53 void ProxyObject::init(const BaseProxyHandler* handler, HandleValue priv, 54 JSContext* cx) { 55 setInlineValueArray(); 56 57 detail::ProxyValueArray* values = detail::GetProxyDataLayout(this)->values(); 58 values->init(numReservedSlots()); 59 60 data.handler = handler; 61 62 if (IsCrossCompartmentWrapper(this)) { 63 MOZ_ASSERT(cx->global() == &cx->compartment()->globalForNewCCW()); 64 setCrossCompartmentPrivate(priv); 65 } else { 66 setSameCompartmentPrivate(priv); 67 } 68 69 // The expando slot is nullptr until required by the installation of 70 // a private field. 71 setExpando(nullptr); 72 } 73 74 /* static */ 75 ProxyObject* ProxyObject::New(JSContext* cx, const BaseProxyHandler* handler, 76 HandleValue priv, TaggedProto proto_, 77 const JSClass* clasp) { 78 Rooted<TaggedProto> proto(cx, proto_); 79 80 MOZ_ASSERT(!clasp->isNativeObject()); 81 MOZ_ASSERT(clasp->isProxyObject()); 82 MOZ_ASSERT(isValidProxyClass(clasp)); 83 MOZ_ASSERT(clasp->shouldDelayMetadataBuilder()); 84 MOZ_ASSERT_IF(proto.isObject(), 85 cx->compartment() == proto.toObject()->compartment()); 86 MOZ_ASSERT(clasp->hasFinalize()); 87 88 #ifdef DEBUG 89 if (priv.isGCThing()) { 90 JS::AssertCellIsNotGray(priv.toGCThing()); 91 } 92 #endif 93 94 gc::AllocKind allocKind = GetProxyGCObjectKind(clasp, handler, priv, 95 /* withInlineValues = */ true); 96 97 Realm* realm = cx->realm(); 98 99 AutoSetNewObjectMetadata metadata(cx); 100 // Try to look up the shape in the NewProxyCache. 101 Rooted<Shape*> shape(cx); 102 if (!realm->newProxyCache.lookup(clasp, proto, shape.address())) { 103 shape = ProxyShape::getShape(cx, clasp, realm, proto, ObjectFlags()); 104 if (!shape) { 105 return nullptr; 106 } 107 108 realm->newProxyCache.add(shape); 109 } 110 111 MOZ_ASSERT(shape->realm() == realm); 112 MOZ_ASSERT(!IsAboutToBeFinalizedUnbarriered(shape.get())); 113 114 // Ensure that the wrapper has the same lifetime assumptions as the 115 // wrappee. Prefer to allocate in the nursery, when possible. 116 gc::Heap heap; 117 if ((priv.isGCThing() && priv.toGCThing()->isTenured()) || 118 !handler->canNurseryAllocate()) { 119 heap = gc::Heap::Tenured; 120 } else { 121 heap = gc::Heap::Default; 122 } 123 124 debugCheckNewObject(shape, allocKind, heap); 125 126 ProxyObject* proxy = cx->newCell<ProxyObject>(allocKind, heap, clasp); 127 if (!proxy) { 128 return nullptr; 129 } 130 131 proxy->initShape(shape); 132 133 MOZ_ASSERT(clasp->shouldDelayMetadataBuilder()); 134 realm->setObjectPendingMetadata(proxy); 135 136 gc::gcprobes::CreateObject(proxy); 137 138 proxy->init(handler, priv, cx); 139 140 return proxy; 141 } 142 143 gc::AllocKind ProxyObject::allocKindForTenure() const { 144 Value priv = private_(); 145 return GetProxyGCObjectKind(getClass(), data.handler, priv, 146 usingInlineValueArray()); 147 } 148 149 void ProxyObject::setCrossCompartmentPrivate(const Value& priv) { 150 setPrivate(priv); 151 } 152 153 void ProxyObject::setSameCompartmentPrivate(const Value& priv) { 154 MOZ_ASSERT(IsObjectValueInCompartment(priv, compartment())); 155 setPrivate(priv); 156 } 157 158 inline void ProxyObject::setPrivate(const Value& priv) { 159 #ifdef DEBUG 160 JS::AssertValueIsNotGray(priv); 161 #endif 162 *slotOfPrivate() = priv; 163 } 164 165 void ProxyObject::setExpando(JSObject* expando) { 166 // Ensure we're in the same compartment as the proxy object: Don't want the 167 // expando to end up as a CCW. 168 MOZ_ASSERT_IF(expando, expando->compartment() == compartment()); 169 170 // Ensure that we don't accidentally end up pointing to a 171 // grey object, which would violate GC invariants. 172 MOZ_ASSERT_IF(!zone()->isGCPreparing() && isMarkedBlack() && expando, 173 !JS::GCThingIsMarkedGray(JS::GCCellPtr(expando))); 174 175 *slotOfExpando() = ObjectOrNullValue(expando); 176 } 177 178 void ProxyObject::nuke() { 179 // Notify the zone that a delegate is no longer a delegate. Be careful not to 180 // expose this pointer, because it has already been removed from the wrapper 181 // map yet we have assertions during tracing that will verify that it is 182 // still present. 183 JSObject* delegate = UncheckedUnwrapWithoutExpose(this); 184 if (delegate != this) { 185 delegate->zone()->beforeClearDelegate(this, delegate); 186 } 187 188 // Clear the target reference and replaced it with a value that encodes 189 // various information about the original target. 190 setSameCompartmentPrivate(DeadProxyTargetValue(this)); 191 192 // Clear out the expando 193 setExpando(nullptr); 194 195 // Update the handler to make this a DeadObjectProxy. 196 setHandler(&DeadObjectProxy::singleton); 197 198 // The proxy's reserved slots are not cleared and will continue to be 199 // traced. This avoids the possibility of triggering write barriers while 200 // nuking proxies in dead compartments which could otherwise cause those 201 // compartments to be kept alive. Note that these are slots cannot hold 202 // cross compartment pointers, so this cannot cause the target compartment 203 // to leak. 204 } 205 206 JS_PUBLIC_API void js::detail::SetValueInProxy(Value* slot, 207 const Value& value) { 208 // Slots in proxies are not GCPtr<Value>s, so do a cast whenever assigning 209 // values to them which might trigger a barrier. 210 *reinterpret_cast<GCPtr<Value>*>(slot) = value; 211 }