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