Bug 1865597 - Add error checking when initializing parallel marking and disable on...
[gecko.git] / js / src / vm / PromiseLookup.cpp
bloba2c232cc3e37e2af471fb8652990e49242069fc7
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;
29 using JS::Value;
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) {
45 JSFunction* fun;
46 if (!IsFunctionObject(obj->getSlot(slot), &fun)) {
47 return false;
49 return fun->maybeNative() == native && fun->realm() == cx->realm();
52 bool js::PromiseLookup::isAccessorPropertyNative(JSContext* cx,
53 NativeObject* holder,
54 uint32_t getterSlot,
55 JSNative native) {
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);
67 // Check condition 1:
68 // Leave the cache uninitialized if the Promise class itself is not yet
69 // initialized.
70 if (!promiseProto) {
71 return;
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 "
78 "initialized");
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;
84 // Check condition 2:
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()) {
89 return;
92 // Get the referred value, and ensure it holds the canonical Promise
93 // constructor.
94 JSFunction* ctorFun;
95 if (!IsFunctionObject(promiseProto->getSlot(ctorProp->slot()), &ctorFun)) {
96 return;
98 if (ctorFun != promiseCtor) {
99 return;
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()) {
107 return;
110 // Get the referred value, and ensure it holds the canonical "then"
111 // function.
112 if (!isDataPropertyNative(cx, promiseProto, thenProp->slot(), Promise_then)) {
113 return;
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)) {
121 return;
124 // Get the referred value, ensure it holds the canonical Promise[@@species]
125 // function.
126 uint32_t speciesGetterSlot = speciesProp->slot();
127 if (!isAccessorPropertyNative(cx, promiseCtor, speciesGetterSlot,
128 Promise_static_species)) {
129 return;
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()) {
137 return;
140 // Get the referred value, and ensure it holds the canonical "resolve"
141 // function.
142 if (!isDataPropertyNative(cx, promiseCtor, resolveProp->slot(),
143 Promise_static_resolve)) {
144 return;
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_) {
178 return false;
181 // Ensure that Promise still has the expected shape.
182 if (promiseCtor->shape() != promiseConstructorShape_) {
183 return false;
186 // Ensure that Promise.prototype.constructor is the canonical constructor.
187 if (promiseProto->getSlot(promiseProtoConstructorSlot_) !=
188 ObjectValue(*promiseCtor)) {
189 return false;
192 // Ensure that Promise.prototype.then is the canonical "then" function.
193 if (!isDataPropertyNative(cx, promiseProto, promiseProtoThenSlot_,
194 Promise_then)) {
195 return false;
198 // Ensure the species getter contains the canonical @@species function.
199 if (!isAccessorPropertyNative(cx, promiseCtor, promiseSpeciesGetterSlot_,
200 Promise_static_species)) {
201 return false;
204 // Ensure that Promise.resolve is the canonical "resolve" function.
205 if (!isDataPropertyNative(cx, promiseCtor, promiseResolveSlot_,
206 Promise_static_resolve)) {
207 return false;
210 return true;
213 bool js::PromiseLookup::ensureInitialized(JSContext* cx,
214 Reinitialize reinitialize) {
215 if (state_ == State::Uninitialized) {
216 // If the cache is not initialized, initialize it.
217 initialize(cx);
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.
222 reset();
223 initialize(cx);
225 } else {
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
233 // optimize.
234 if (state_ != State::Initialized) {
235 return false;
238 // By the time we get here, we should have a sane promise state.
239 MOZ_ASSERT(isPromiseStateStillSane(cx));
241 return true;
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)) {
254 return false;
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)) {
268 return false;
271 // The object uses the default properties from Promise.prototype.
272 return hasDefaultProtoAndNoShadowedProperties(cx, promise);