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/Watchtower.h"
8
9 #include "js/CallAndConstruct.h"
10 #include "js/Id.h"
11 #include "vm/Compartment.h"
12 #include "vm/JSContext.h"
13 #include "vm/JSObject.h"
14 #include "vm/NativeObject.h"
15 #include "vm/Realm.h"
16
17 #include "vm/Compartment-inl.h"
18 #include "vm/JSObject-inl.h"
19
20 using namespace js;
21
InvokeWatchtowerCallback(JSContext * cx,const char * kind,HandleObject obj,HandleValue extra)22 static bool InvokeWatchtowerCallback(JSContext* cx, const char* kind,
23 HandleObject obj, HandleValue extra) {
24 // Invoke the callback set by the setWatchtowerCallback testing function with
25 // arguments (kind, obj, extra).
26
27 if (!cx->watchtowerTestingCallbackRef()) {
28 return true;
29 }
30
31 RootedString kindString(cx, NewStringCopyZ<CanGC>(cx, kind));
32 if (!kindString) {
33 return false;
34 }
35
36 constexpr size_t NumArgs = 3;
37 JS::RootedValueArray<NumArgs> argv(cx);
38 argv[0].setString(kindString);
39 argv[1].setObject(*obj);
40 argv[2].set(extra);
41
42 RootedValue funVal(cx, ObjectValue(*cx->watchtowerTestingCallbackRef()));
43 AutoRealm ar(cx, &funVal.toObject());
44
45 for (size_t i = 0; i < NumArgs; i++) {
46 if (!cx->compartment()->wrap(cx, argv[i])) {
47 return false;
48 }
49 }
50
51 RootedValue rval(cx);
52 return JS_CallFunctionValue(cx, nullptr, funVal, HandleValueArray(argv),
53 &rval);
54 }
55
ReshapeForShadowedProp(JSContext * cx,HandleNativeObject obj,HandleId id)56 static bool ReshapeForShadowedProp(JSContext* cx, HandleNativeObject obj,
57 HandleId id) {
58 // |obj| has been used as the prototype of another object. Check if we're
59 // shadowing a property on its proto chain. In this case we need to reshape
60 // that object for shape teleporting to work correctly.
61 //
62 // See also the 'Shape Teleporting Optimization' comment in jit/CacheIR.cpp.
63
64 MOZ_ASSERT(obj->isUsedAsPrototype());
65
66 // Lookups on integer ids cannot be cached through prototypes.
67 if (id.isInt()) {
68 return true;
69 }
70
71 RootedObject proto(cx, obj->staticPrototype());
72 while (proto) {
73 // Lookups will not be cached through non-native protos.
74 if (!proto->is<NativeObject>()) {
75 break;
76 }
77
78 if (proto->as<NativeObject>().contains(cx, id)) {
79 return JSObject::setInvalidatedTeleporting(cx, proto);
80 }
81
82 proto = proto->staticPrototype();
83 }
84
85 return true;
86 }
87
InvalidateMegamorphicCache(JSContext * cx,HandleNativeObject obj)88 static void InvalidateMegamorphicCache(JSContext* cx, HandleNativeObject obj) {
89 // The megamorphic cache only checks the receiver object's shape. We need to
90 // invalidate the cache when a prototype object changes its set of properties,
91 // to account for cached properties that are deleted, turned into an accessor
92 // property, or shadowed by another object on the proto chain.
93
94 MOZ_ASSERT(obj->isUsedAsPrototype());
95
96 cx->caches().megamorphicCache.bumpGeneration();
97 }
98
99 // static
watchPropertyAddSlow(JSContext * cx,HandleNativeObject obj,HandleId id)100 bool Watchtower::watchPropertyAddSlow(JSContext* cx, HandleNativeObject obj,
101 HandleId id) {
102 MOZ_ASSERT(watchesPropertyAdd(obj));
103
104 if (obj->isUsedAsPrototype()) {
105 if (!ReshapeForShadowedProp(cx, obj, id)) {
106 return false;
107 }
108 if (!id.isInt()) {
109 InvalidateMegamorphicCache(cx, obj);
110 }
111 }
112
113 if (MOZ_UNLIKELY(obj->useWatchtowerTestingCallback())) {
114 RootedValue val(cx, IdToValue(id));
115 if (!InvokeWatchtowerCallback(cx, "add-prop", obj, val)) {
116 return false;
117 }
118 }
119
120 return true;
121 }
122
ReshapeForProtoMutation(JSContext * cx,HandleObject obj)123 static bool ReshapeForProtoMutation(JSContext* cx, HandleObject obj) {
124 // To avoid the JIT guarding on each prototype in the proto chain to detect
125 // prototype mutation, we can instead reshape the rest of the proto chain such
126 // that a guard on any of them is sufficient. To avoid excessive reshaping and
127 // invalidation, we apply heuristics to decide when to apply this and when
128 // to require a guard.
129 //
130 // There are two cases:
131 //
132 // (1) The object is not marked IsUsedAsPrototype. This is the common case.
133 // Because shape implies proto, we rely on the caller changing the
134 // object's shape. The JIT guards on this object's shape or prototype so
135 // there's nothing we have to do here for objects on the proto chain.
136 //
137 // (2) The object is marked IsUsedAsPrototype. This implies the object may be
138 // participating in shape teleporting. To invalidate JIT ICs depending on
139 // the proto chain being unchanged, set the InvalidatedTeleporting shape
140 // flag for this object and objects on its proto chain.
141 //
142 // This flag disables future shape teleporting attempts, so next time this
143 // happens the loop below will be a no-op.
144 //
145 // NOTE: We only handle NativeObjects and don't propagate reshapes through
146 // any non-native objects on the chain.
147 //
148 // See Also:
149 // - GeneratePrototypeGuards
150 // - GeneratePrototypeHoleGuards
151
152 MOZ_ASSERT(obj->isUsedAsPrototype());
153
154 RootedObject pobj(cx, obj);
155
156 while (pobj && pobj->is<NativeObject>()) {
157 if (!pobj->hasInvalidatedTeleporting()) {
158 if (!JSObject::setInvalidatedTeleporting(cx, pobj)) {
159 return false;
160 }
161 }
162 pobj = pobj->staticPrototype();
163 }
164
165 return true;
166 }
167
168 // static
watchProtoChangeSlow(JSContext * cx,HandleObject obj)169 bool Watchtower::watchProtoChangeSlow(JSContext* cx, HandleObject obj) {
170 MOZ_ASSERT(watchesProtoChange(obj));
171
172 if (obj->isUsedAsPrototype()) {
173 if (!ReshapeForProtoMutation(cx, obj)) {
174 return false;
175 }
176 if (obj->is<NativeObject>()) {
177 InvalidateMegamorphicCache(cx, obj.as<NativeObject>());
178 }
179 }
180
181 if (MOZ_UNLIKELY(obj->useWatchtowerTestingCallback())) {
182 if (!InvokeWatchtowerCallback(cx, "proto-change", obj,
183 JS::UndefinedHandleValue)) {
184 return false;
185 }
186 }
187
188 return true;
189 }
190
191 // static
watchPropertyRemoveSlow(JSContext * cx,HandleNativeObject obj,HandleId id)192 bool Watchtower::watchPropertyRemoveSlow(JSContext* cx, HandleNativeObject obj,
193 HandleId id) {
194 MOZ_ASSERT(watchesPropertyRemove(obj));
195
196 if (obj->isUsedAsPrototype() && !id.isInt()) {
197 InvalidateMegamorphicCache(cx, obj);
198 }
199
200 if (MOZ_UNLIKELY(obj->useWatchtowerTestingCallback())) {
201 RootedValue val(cx, IdToValue(id));
202 if (!InvokeWatchtowerCallback(cx, "remove-prop", obj, val)) {
203 return false;
204 }
205 }
206
207 return true;
208 }
209
210 // static
watchPropertyChangeSlow(JSContext * cx,HandleNativeObject obj,HandleId id)211 bool Watchtower::watchPropertyChangeSlow(JSContext* cx, HandleNativeObject obj,
212 HandleId id) {
213 MOZ_ASSERT(watchesPropertyChange(obj));
214
215 if (obj->isUsedAsPrototype() && !id.isInt()) {
216 InvalidateMegamorphicCache(cx, obj);
217 }
218
219 if (MOZ_UNLIKELY(obj->useWatchtowerTestingCallback())) {
220 RootedValue val(cx, IdToValue(id));
221 if (!InvokeWatchtowerCallback(cx, "change-prop", obj, val)) {
222 return false;
223 }
224 }
225
226 return true;
227 }
228
229 // static
watchFreezeOrSealSlow(JSContext * cx,HandleNativeObject obj)230 bool Watchtower::watchFreezeOrSealSlow(JSContext* cx, HandleNativeObject obj) {
231 MOZ_ASSERT(watchesFreezeOrSeal(obj));
232
233 if (MOZ_UNLIKELY(obj->useWatchtowerTestingCallback())) {
234 if (!InvokeWatchtowerCallback(cx, "freeze-or-seal", obj,
235 JS::UndefinedHandleValue)) {
236 return false;
237 }
238 }
239
240 return true;
241 }
242
243 // static
watchObjectSwapSlow(JSContext * cx,HandleObject a,HandleObject b)244 bool Watchtower::watchObjectSwapSlow(JSContext* cx, HandleObject a,
245 HandleObject b) {
246 MOZ_ASSERT(watchesObjectSwap(a, b));
247
248 if (a->isUsedAsPrototype() && a->is<NativeObject>()) {
249 InvalidateMegamorphicCache(cx, a.as<NativeObject>());
250 }
251 if (b->isUsedAsPrototype() && b->is<NativeObject>()) {
252 InvalidateMegamorphicCache(cx, b.as<NativeObject>());
253 }
254
255 if (MOZ_UNLIKELY(a->useWatchtowerTestingCallback() ||
256 b->useWatchtowerTestingCallback())) {
257 RootedValue extra(cx, ObjectValue(*b));
258 if (!InvokeWatchtowerCallback(cx, "object-swap", a, extra)) {
259 // The JSObject::swap caller unfortunately assumes failures are OOM and
260 // crashes. Ignore non-OOM exceptions for now.
261 if (cx->isThrowingOutOfMemory()) {
262 return false;
263 }
264 cx->clearPendingException();
265 }
266 }
267
268 return true;
269 }
270