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/. */
7 #include "vm/PromiseLookup.h"
9 #include "mozilla/Assertions.h" // MOZ_ASSERT
11 #include "jspubtd.h" // JSProto_*
13 #include "builtin/Promise.h" // js::Promise_then, js::Promise_static_resolve, js::Promise_static_species
14 #include "js/HeapAPI.h" // js::gc::IsInsideNursery
15 #include "js/Id.h" // JS::PropertyKey
16 #include "js/Value.h" // JS::Value, JS::ObjectValue
17 #include "util/Poison.h" // js::AlwaysPoison, JS_RESET_VALUE_PATTERN, MemCheckKind
18 #include "vm/GlobalObject.h" // js::GlobalObject
19 #include "vm/JSContext.h" // JSContext
20 #include "vm/JSFunction.h" // JSFunction
21 #include "vm/JSObject.h" // JSObject
22 #include "vm/NativeObject.h" // js::NativeObject
23 #include "vm/Runtime.h" // js::WellKnownSymbols
24 #include "vm/Shape.h" // js::Shape
26 #include "vm/JSObject-inl.h" // js::IsFunctionObject, js::IsNativeFunction
28 using JS::ObjectValue
;
31 using js::NativeObject
;
33 JSFunction
* js::PromiseLookup::getPromiseConstructor(JSContext
* cx
) {
34 JSObject
* obj
= cx
->global()->maybeGetConstructor(JSProto_Promise
);
35 return obj
? &obj
->as
<JSFunction
>() : nullptr;
38 NativeObject
* js::PromiseLookup::getPromisePrototype(JSContext
* cx
) {
39 JSObject
* obj
= cx
->global()->maybeGetPrototype(JSProto_Promise
);
40 return obj
? &obj
->as
<NativeObject
>() : nullptr;
43 bool js::PromiseLookup::isDataPropertyNative(JSContext
* cx
, NativeObject
* obj
,
44 uint32_t slot
, JSNative native
) {
46 if (!IsFunctionObject(obj
->getSlot(slot
), &fun
)) {
49 return fun
->maybeNative() == native
&& fun
->realm() == cx
->realm();
52 bool js::PromiseLookup::isAccessorPropertyNative(JSContext
* cx
,
56 JSObject
* getter
= holder
->getGetter(getterSlot
);
57 return getter
&& IsNativeFunction(getter
, native
) &&
58 getter
->as
<JSFunction
>().realm() == cx
->realm();
61 void js::PromiseLookup::initialize(JSContext
* cx
) {
62 MOZ_ASSERT(state_
== State::Uninitialized
);
64 // Get the canonical Promise.prototype.
65 NativeObject
* promiseProto
= getPromisePrototype(cx
);
68 // Leave the cache uninitialized if the Promise class itself is not yet
74 // Get the canonical Promise constructor.
75 JSFunction
* promiseCtor
= getPromiseConstructor(cx
);
76 MOZ_ASSERT(promiseCtor
,
77 "The Promise constructor is initialized iff Promise.prototype is "
80 // Shortcut returns below means Promise[@@species] will never be
81 // optimizable, set to disabled now, and clear it later when we succeed.
82 state_
= State::Disabled
;
85 // Look up Promise.prototype.constructor and ensure it's a data property.
86 mozilla::Maybe
<PropertyInfo
> ctorProp
=
87 promiseProto
->lookup(cx
, cx
->names().constructor
);
88 if (ctorProp
.isNothing() || !ctorProp
->isDataProperty()) {
92 // Get the referred value, and ensure it holds the canonical Promise
95 if (!IsFunctionObject(promiseProto
->getSlot(ctorProp
->slot()), &ctorFun
)) {
98 if (ctorFun
!= promiseCtor
) {
102 // Check condition 3:
103 // Look up Promise.prototype.then and ensure it's a data property.
104 mozilla::Maybe
<PropertyInfo
> thenProp
=
105 promiseProto
->lookup(cx
, cx
->names().then
);
106 if (thenProp
.isNothing() || !thenProp
->isDataProperty()) {
110 // Get the referred value, and ensure it holds the canonical "then"
112 if (!isDataPropertyNative(cx
, promiseProto
, thenProp
->slot(), Promise_then
)) {
116 // Check condition 4:
117 // Look up the '@@species' value on Promise.
118 mozilla::Maybe
<PropertyInfo
> speciesProp
= promiseCtor
->lookup(
119 cx
, PropertyKey::Symbol(cx
->wellKnownSymbols().species
));
120 if (speciesProp
.isNothing() || !promiseCtor
->hasGetter(*speciesProp
)) {
124 // Get the referred value, ensure it holds the canonical Promise[@@species]
126 uint32_t speciesGetterSlot
= speciesProp
->slot();
127 if (!isAccessorPropertyNative(cx
, promiseCtor
, speciesGetterSlot
,
128 Promise_static_species
)) {
132 // Check condition 5:
133 // Look up Promise.resolve and ensure it's a data property.
134 mozilla::Maybe
<PropertyInfo
> resolveProp
=
135 promiseCtor
->lookup(cx
, cx
->names().resolve
);
136 if (resolveProp
.isNothing() || !resolveProp
->isDataProperty()) {
140 // Get the referred value, and ensure it holds the canonical "resolve"
142 if (!isDataPropertyNative(cx
, promiseCtor
, resolveProp
->slot(),
143 Promise_static_resolve
)) {
147 // Store raw pointers below. This is okay to do here, because all objects
148 // are in the tenured heap.
149 MOZ_ASSERT(!gc::IsInsideNursery(promiseCtor
->shape()));
150 MOZ_ASSERT(!gc::IsInsideNursery(promiseProto
->shape()));
152 state_
= State::Initialized
;
153 promiseConstructorShape_
= promiseCtor
->shape();
154 promiseProtoShape_
= promiseProto
->shape();
155 promiseSpeciesGetterSlot_
= speciesGetterSlot
;
156 promiseResolveSlot_
= resolveProp
->slot();
157 promiseProtoConstructorSlot_
= ctorProp
->slot();
158 promiseProtoThenSlot_
= thenProp
->slot();
161 void js::PromiseLookup::reset() {
162 AlwaysPoison(this, JS_RESET_VALUE_PATTERN
, sizeof(*this),
163 MemCheckKind::MakeUndefined
);
164 state_
= State::Uninitialized
;
167 bool js::PromiseLookup::isPromiseStateStillSane(JSContext
* cx
) {
168 MOZ_ASSERT(state_
== State::Initialized
);
170 NativeObject
* promiseProto
= getPromisePrototype(cx
);
171 MOZ_ASSERT(promiseProto
);
173 NativeObject
* promiseCtor
= getPromiseConstructor(cx
);
174 MOZ_ASSERT(promiseCtor
);
176 // Ensure that Promise.prototype still has the expected shape.
177 if (promiseProto
->shape() != promiseProtoShape_
) {
181 // Ensure that Promise still has the expected shape.
182 if (promiseCtor
->shape() != promiseConstructorShape_
) {
186 // Ensure that Promise.prototype.constructor is the canonical constructor.
187 if (promiseProto
->getSlot(promiseProtoConstructorSlot_
) !=
188 ObjectValue(*promiseCtor
)) {
192 // Ensure that Promise.prototype.then is the canonical "then" function.
193 if (!isDataPropertyNative(cx
, promiseProto
, promiseProtoThenSlot_
,
198 // Ensure the species getter contains the canonical @@species function.
199 if (!isAccessorPropertyNative(cx
, promiseCtor
, promiseSpeciesGetterSlot_
,
200 Promise_static_species
)) {
204 // Ensure that Promise.resolve is the canonical "resolve" function.
205 if (!isDataPropertyNative(cx
, promiseCtor
, promiseResolveSlot_
,
206 Promise_static_resolve
)) {
213 bool js::PromiseLookup::ensureInitialized(JSContext
* cx
,
214 Reinitialize reinitialize
) {
215 if (state_
== State::Uninitialized
) {
216 // If the cache is not initialized, initialize it.
218 } else if (state_
== State::Initialized
) {
219 if (reinitialize
== Reinitialize::Allowed
) {
220 if (!isPromiseStateStillSane(cx
)) {
221 // If the promise state is no longer sane, reinitialize.
226 // When we're not allowed to reinitialize, the promise state must
227 // still be sane if the cache is already initialized.
228 MOZ_ASSERT(isPromiseStateStillSane(cx
));
232 // If the cache is disabled or still uninitialized, don't bother trying to
234 if (state_
!= State::Initialized
) {
238 // By the time we get here, we should have a sane promise state.
239 MOZ_ASSERT(isPromiseStateStillSane(cx
));
244 bool js::PromiseLookup::isDefaultPromiseState(JSContext
* cx
) {
245 // Promise and Promise.prototype are in their default states iff the
246 // lookup cache was successfully initialized.
247 return ensureInitialized(cx
, Reinitialize::Allowed
);
250 bool js::PromiseLookup::hasDefaultProtoAndNoShadowedProperties(
251 JSContext
* cx
, PromiseObject
* promise
) {
252 // Ensure |promise|'s prototype is the actual Promise.prototype.
253 if (promise
->staticPrototype() != getPromisePrototype(cx
)) {
257 // Ensure |promise| doesn't define any own properties. This serves as a
258 // quick check to make sure |promise| doesn't define an own "constructor"
259 // or "then" property which may shadow Promise.prototype.constructor or
260 // Promise.prototype.then.
261 return promise
->empty();
264 bool js::PromiseLookup::isDefaultInstance(JSContext
* cx
, PromiseObject
* promise
,
265 Reinitialize reinitialize
) {
266 // Promise and Promise.prototype must be in their default states.
267 if (!ensureInitialized(cx
, reinitialize
)) {
271 // The object uses the default properties from Promise.prototype.
272 return hasDefaultProtoAndNoShadowedProperties(cx
, promise
);