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/PromiseLookup.h"
8
9 #include "mozilla/Assertions.h" // MOZ_ASSERT
10
11 #include "builtin/Promise.h" // js::Promise_then, js::Promise_static_resolve, js::Promise_static_species
12 #include "js/HeapAPI.h" // js::gc::IsInsideNursery
13 #include "js/Id.h" // SYMBOL_TO_JSID
14 #include "js/ProtoKey.h" // JSProto_*
15 #include "js/Value.h" // JS::Value, JS::ObjectValue
16 #include "util/Poison.h" // js::AlwaysPoison, JS_RESET_VALUE_PATTERN, MemCheckKind
17 #include "vm/GlobalObject.h" // js::GlobalObject
18 #include "vm/JSContext.h" // JSContext
19 #include "vm/JSFunction.h" // JSFunction
20 #include "vm/JSObject.h" // JSObject
21 #include "vm/NativeObject.h" // js::NativeObject
22 #include "vm/Runtime.h" // js::WellKnownSymbols
23 #include "vm/Shape.h" // js::Shape
24
25 #include "vm/JSObject-inl.h" // js::IsFunctionObject, js::IsNativeFunction
26
27 using JS::ObjectValue;
28 using JS::Value;
29
30 using js::NativeObject;
31
getPromiseConstructor(JSContext * cx)32 JSFunction* js::PromiseLookup::getPromiseConstructor(JSContext* cx) {
33 const Value& val = cx->global()->getConstructor(JSProto_Promise);
34 return val.isObject() ? &val.toObject().as<JSFunction>() : nullptr;
35 }
36
getPromisePrototype(JSContext * cx)37 NativeObject* js::PromiseLookup::getPromisePrototype(JSContext* cx) {
38 const Value& val = cx->global()->getPrototype(JSProto_Promise);
39 return val.isObject() ? &val.toObject().as<NativeObject>() : nullptr;
40 }
41
isDataPropertyNative(JSContext * cx,NativeObject * obj,uint32_t slot,JSNative native)42 bool js::PromiseLookup::isDataPropertyNative(JSContext* cx, NativeObject* obj,
43 uint32_t slot, JSNative native) {
44 JSFunction* fun;
45 if (!IsFunctionObject(obj->getSlot(slot), &fun)) {
46 return false;
47 }
48 return fun->maybeNative() == native && fun->realm() == cx->realm();
49 }
50
isAccessorPropertyNative(JSContext * cx,NativeObject * holder,uint32_t getterSlot,JSNative native)51 bool js::PromiseLookup::isAccessorPropertyNative(JSContext* cx,
52 NativeObject* holder,
53 uint32_t getterSlot,
54 JSNative native) {
55 JSObject* getter = holder->getGetter(getterSlot);
56 return getter && IsNativeFunction(getter, native) &&
57 getter->as<JSFunction>().realm() == cx->realm();
58 }
59
initialize(JSContext * cx)60 void js::PromiseLookup::initialize(JSContext* cx) {
61 MOZ_ASSERT(state_ == State::Uninitialized);
62
63 // Get the canonical Promise.prototype.
64 NativeObject* promiseProto = getPromisePrototype(cx);
65
66 // Check condition 1:
67 // Leave the cache uninitialized if the Promise class itself is not yet
68 // initialized.
69 if (!promiseProto) {
70 return;
71 }
72
73 // Get the canonical Promise constructor.
74 JSFunction* promiseCtor = getPromiseConstructor(cx);
75 MOZ_ASSERT(promiseCtor,
76 "The Promise constructor is initialized iff Promise.prototype is "
77 "initialized");
78
79 // Shortcut returns below means Promise[@@species] will never be
80 // optimizable, set to disabled now, and clear it later when we succeed.
81 state_ = State::Disabled;
82
83 // Check condition 2:
84 // Look up Promise.prototype.constructor and ensure it's a data property.
85 mozilla::Maybe<PropertyInfo> ctorProp =
86 promiseProto->lookup(cx, cx->names().constructor);
87 if (ctorProp.isNothing() || !ctorProp->isDataProperty()) {
88 return;
89 }
90
91 // Get the referred value, and ensure it holds the canonical Promise
92 // constructor.
93 JSFunction* ctorFun;
94 if (!IsFunctionObject(promiseProto->getSlot(ctorProp->slot()), &ctorFun)) {
95 return;
96 }
97 if (ctorFun != promiseCtor) {
98 return;
99 }
100
101 // Check condition 3:
102 // Look up Promise.prototype.then and ensure it's a data property.
103 mozilla::Maybe<PropertyInfo> thenProp =
104 promiseProto->lookup(cx, cx->names().then);
105 if (thenProp.isNothing() || !thenProp->isDataProperty()) {
106 return;
107 }
108
109 // Get the referred value, and ensure it holds the canonical "then"
110 // function.
111 if (!isDataPropertyNative(cx, promiseProto, thenProp->slot(), Promise_then)) {
112 return;
113 }
114
115 // Check condition 4:
116 // Look up the '@@species' value on Promise.
117 mozilla::Maybe<PropertyInfo> speciesProp =
118 promiseCtor->lookup(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().species));
119 if (speciesProp.isNothing() || !promiseCtor->hasGetter(*speciesProp)) {
120 return;
121 }
122
123 // Get the referred value, ensure it holds the canonical Promise[@@species]
124 // function.
125 uint32_t speciesGetterSlot = speciesProp->slot();
126 if (!isAccessorPropertyNative(cx, promiseCtor, speciesGetterSlot,
127 Promise_static_species)) {
128 return;
129 }
130
131 // Check condition 5:
132 // Look up Promise.resolve and ensure it's a data property.
133 mozilla::Maybe<PropertyInfo> resolveProp =
134 promiseCtor->lookup(cx, cx->names().resolve);
135 if (resolveProp.isNothing() || !resolveProp->isDataProperty()) {
136 return;
137 }
138
139 // Get the referred value, and ensure it holds the canonical "resolve"
140 // function.
141 if (!isDataPropertyNative(cx, promiseCtor, resolveProp->slot(),
142 Promise_static_resolve)) {
143 return;
144 }
145
146 // Store raw pointers below. This is okay to do here, because all objects
147 // are in the tenured heap.
148 MOZ_ASSERT(!gc::IsInsideNursery(promiseCtor->shape()));
149 MOZ_ASSERT(!gc::IsInsideNursery(promiseProto->shape()));
150
151 state_ = State::Initialized;
152 promiseConstructorShape_ = promiseCtor->shape();
153 promiseProtoShape_ = promiseProto->shape();
154 promiseSpeciesGetterSlot_ = speciesGetterSlot;
155 promiseResolveSlot_ = resolveProp->slot();
156 promiseProtoConstructorSlot_ = ctorProp->slot();
157 promiseProtoThenSlot_ = thenProp->slot();
158 }
159
reset()160 void js::PromiseLookup::reset() {
161 AlwaysPoison(this, JS_RESET_VALUE_PATTERN, sizeof(*this),
162 MemCheckKind::MakeUndefined);
163 state_ = State::Uninitialized;
164 }
165
isPromiseStateStillSane(JSContext * cx)166 bool js::PromiseLookup::isPromiseStateStillSane(JSContext* cx) {
167 MOZ_ASSERT(state_ == State::Initialized);
168
169 NativeObject* promiseProto = getPromisePrototype(cx);
170 MOZ_ASSERT(promiseProto);
171
172 NativeObject* promiseCtor = getPromiseConstructor(cx);
173 MOZ_ASSERT(promiseCtor);
174
175 // Ensure that Promise.prototype still has the expected shape.
176 if (promiseProto->shape() != promiseProtoShape_) {
177 return false;
178 }
179
180 // Ensure that Promise still has the expected shape.
181 if (promiseCtor->shape() != promiseConstructorShape_) {
182 return false;
183 }
184
185 // Ensure that Promise.prototype.constructor is the canonical constructor.
186 if (promiseProto->getSlot(promiseProtoConstructorSlot_) !=
187 ObjectValue(*promiseCtor)) {
188 return false;
189 }
190
191 // Ensure that Promise.prototype.then is the canonical "then" function.
192 if (!isDataPropertyNative(cx, promiseProto, promiseProtoThenSlot_,
193 Promise_then)) {
194 return false;
195 }
196
197 // Ensure the species getter contains the canonical @@species function.
198 if (!isAccessorPropertyNative(cx, promiseCtor, promiseSpeciesGetterSlot_,
199 Promise_static_species)) {
200 return false;
201 }
202
203 // Ensure that Promise.resolve is the canonical "resolve" function.
204 if (!isDataPropertyNative(cx, promiseCtor, promiseResolveSlot_,
205 Promise_static_resolve)) {
206 return false;
207 }
208
209 return true;
210 }
211
ensureInitialized(JSContext * cx,Reinitialize reinitialize)212 bool js::PromiseLookup::ensureInitialized(JSContext* cx,
213 Reinitialize reinitialize) {
214 if (state_ == State::Uninitialized) {
215 // If the cache is not initialized, initialize it.
216 initialize(cx);
217 } else if (state_ == State::Initialized) {
218 if (reinitialize == Reinitialize::Allowed) {
219 if (!isPromiseStateStillSane(cx)) {
220 // If the promise state is no longer sane, reinitialize.
221 reset();
222 initialize(cx);
223 }
224 } else {
225 // When we're not allowed to reinitialize, the promise state must
226 // still be sane if the cache is already initialized.
227 MOZ_ASSERT(isPromiseStateStillSane(cx));
228 }
229 }
230
231 // If the cache is disabled or still uninitialized, don't bother trying to
232 // optimize.
233 if (state_ != State::Initialized) {
234 return false;
235 }
236
237 // By the time we get here, we should have a sane promise state.
238 MOZ_ASSERT(isPromiseStateStillSane(cx));
239
240 return true;
241 }
242
isDefaultPromiseState(JSContext * cx)243 bool js::PromiseLookup::isDefaultPromiseState(JSContext* cx) {
244 // Promise and Promise.prototype are in their default states iff the
245 // lookup cache was successfully initialized.
246 return ensureInitialized(cx, Reinitialize::Allowed);
247 }
248
hasDefaultProtoAndNoShadowedProperties(JSContext * cx,PromiseObject * promise)249 bool js::PromiseLookup::hasDefaultProtoAndNoShadowedProperties(
250 JSContext* cx, PromiseObject* promise) {
251 // Ensure |promise|'s prototype is the actual Promise.prototype.
252 if (promise->staticPrototype() != getPromisePrototype(cx)) {
253 return false;
254 }
255
256 // Ensure |promise| doesn't define any own properties. This serves as a
257 // quick check to make sure |promise| doesn't define an own "constructor"
258 // or "then" property which may shadow Promise.prototype.constructor or
259 // Promise.prototype.then.
260 return promise->empty();
261 }
262
isDefaultInstance(JSContext * cx,PromiseObject * promise,Reinitialize reinitialize)263 bool js::PromiseLookup::isDefaultInstance(JSContext* cx, PromiseObject* promise,
264 Reinitialize reinitialize) {
265 // Promise and Promise.prototype must be in their default states.
266 if (!ensureInitialized(cx, reinitialize)) {
267 return false;
268 }
269
270 // The object uses the default properties from Promise.prototype.
271 return hasDefaultProtoAndNoShadowedProperties(cx, promise);
272 }
273