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