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