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 #ifndef vm_PromiseLookup_h
8 #define vm_PromiseLookup_h
9 
10 #include "mozilla/Attributes.h"  // MOZ_NON_TEMPORARY_CLASS, MOZ_INIT_OUTSIDE_CTOR
11 
12 #include <stdint.h>  // uint8_t, uint32_t
13 
14 #include "js/CallArgs.h"  // JSNative
15 
16 struct JS_PUBLIC_API JSContext;
17 
18 class JSFunction;
19 
20 namespace js {
21 
22 class NativeObject;
23 class PromiseObject;
24 class Shape;
25 
26 class MOZ_NON_TEMPORARY_CLASS PromiseLookup final {
27   // clang-format off
28     /*
29      * A PromiseLookup holds the following:
30      *
31      *  Promise's shape (promiseConstructorShape_)
32      *       To ensure that Promise has not been modified.
33      *
34      *  Promise.prototype's shape (promiseProtoShape_)
35      *      To ensure that Promise.prototype has not been modified.
36      *
37      *  Promise's slot number for the @@species getter
38      *  (promiseSpeciesGetterSlot_)
39      *      To quickly retrieve the @@species getter for Promise.
40      *
41      *  Promise's slot number for resolve (promiseResolveSlot_)
42      *      To quickly retrieve the Promise.resolve function.
43      *
44      *  Promise.prototype's slot number for constructor (promiseProtoConstructorSlot_)
45      *      To quickly retrieve the Promise.prototype.constructor property.
46      *
47      *  Promise.prototype's slot number for then (promiseProtoThenSlot_)
48      *      To quickly retrieve the Promise.prototype.then function.
49      *
50      * MOZ_INIT_OUTSIDE_CTOR fields below are set in |initialize()|.  The
51      * constructor only initializes a |state_| field, that defines whether the
52      * other fields are accessible.
53      */
54   // clang-format on
55 
56   // Shape of matching Promise object.
57   MOZ_INIT_OUTSIDE_CTOR Shape* promiseConstructorShape_;
58 
59   // Shape of matching Promise.prototype object.
60   MOZ_INIT_OUTSIDE_CTOR Shape* promiseProtoShape_;
61 
62   // Slot number for the @@species property on the Promise constructor.
63   MOZ_INIT_OUTSIDE_CTOR uint32_t promiseSpeciesGetterSlot_;
64 
65   // Slots Promise.resolve, Promise.prototype.constructor, and
66   // Promise.prototype.then.
67   MOZ_INIT_OUTSIDE_CTOR uint32_t promiseResolveSlot_;
68   MOZ_INIT_OUTSIDE_CTOR uint32_t promiseProtoConstructorSlot_;
69   MOZ_INIT_OUTSIDE_CTOR uint32_t promiseProtoThenSlot_;
70 
71   enum class State : uint8_t {
72     // Flags marking the lazy initialization of the above fields.
73     Uninitialized,
74     Initialized,
75 
76     // The disabled flag is set when we don't want to try optimizing
77     // anymore because core objects were changed.
78     Disabled
79   };
80 
81   State state_ = State::Uninitialized;
82 
83   // Initialize the internal fields.
84   //
85   // The cache is successfully initialized iff
86   // 1. Promise and Promise.prototype classes are initialized.
87   // 2. Promise.prototype.constructor is equal to Promise.
88   // 3. Promise.prototype.then is the original `then` function.
89   // 4. Promise[@@species] is the original @@species getter.
90   // 5. Promise.resolve is the original `resolve` function.
91   void initialize(JSContext* cx);
92 
93   // Reset the cache.
94   void reset();
95 
96   // Check if the global promise-related objects have not been messed with
97   // in a way that would disable this cache.
98   bool isPromiseStateStillSane(JSContext* cx);
99 
100   // Flags to control whether or not ensureInitialized() is allowed to
101   // reinitialize the cache when the Promise state is no longer sane.
102   enum class Reinitialize : bool { Allowed, Disallowed };
103 
104   // Return true if the lookup cache is properly initialized for usage.
105   bool ensureInitialized(JSContext* cx, Reinitialize reinitialize);
106 
107   // Return true if the prototype of the given Promise object is
108   // Promise.prototype and the object doesn't shadow properties from
109   // Promise.prototype.
110   bool hasDefaultProtoAndNoShadowedProperties(JSContext* cx,
111                                               PromiseObject* promise);
112 
113   // Return true if the given Promise object uses the default @@species,
114   // "constructor", and "then" properties.
115   bool isDefaultInstance(JSContext* cx, PromiseObject* promise,
116                          Reinitialize reinitialize);
117 
118   // Return the built-in Promise constructor or null if not yet initialized.
119   static JSFunction* getPromiseConstructor(JSContext* cx);
120 
121   // Return the built-in Promise prototype or null if not yet initialized.
122   static NativeObject* getPromisePrototype(JSContext* cx);
123 
124   // Return true if the slot contains the given native.
125   static bool isDataPropertyNative(JSContext* cx, NativeObject* obj,
126                                    uint32_t slot, JSNative native);
127 
128   // Return true if the accessor shape contains the given native.
129   static bool isAccessorPropertyNative(JSContext* cx, NativeObject* holder,
130                                        uint32_t getterSlot, JSNative native);
131 
132  public:
133   /** Construct a |PromiseSpeciesLookup| in the uninitialized state. */
PromiseLookup()134   PromiseLookup() { reset(); }
135 
136   // Return true if the Promise constructor and Promise.prototype still use
137   // the default built-in functions.
138   bool isDefaultPromiseState(JSContext* cx);
139 
140   // Return true if the given Promise object uses the default @@species,
141   // "constructor", and "then" properties.
isDefaultInstance(JSContext * cx,PromiseObject * promise)142   bool isDefaultInstance(JSContext* cx, PromiseObject* promise) {
143     return isDefaultInstance(cx, promise, Reinitialize::Allowed);
144   }
145 
146   // Return true if the given Promise object uses the default @@species,
147   // "constructor", and "then" properties.
isDefaultInstanceWhenPromiseStateIsSane(JSContext * cx,PromiseObject * promise)148   bool isDefaultInstanceWhenPromiseStateIsSane(JSContext* cx,
149                                                PromiseObject* promise) {
150     return isDefaultInstance(cx, promise, Reinitialize::Disallowed);
151   }
152 
153   // Purge the cache and all info associated with it.
purge()154   void purge() {
155     if (state_ == State::Initialized) {
156       reset();
157     }
158   }
159 };
160 
161 }  // namespace js
162 
163 #endif  // vm_PromiseLookup_h
164