summaryrefslogtreecommitdiffstats
path: root/js/src/vm/PromiseLookup.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/vm/PromiseLookup.cpp
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--js/src/vm/PromiseLookup.cpp272
1 files changed, 272 insertions, 0 deletions
diff --git a/js/src/vm/PromiseLookup.cpp b/js/src/vm/PromiseLookup.cpp
new file mode 100644
index 0000000000..fae97b46d5
--- /dev/null
+++ b/js/src/vm/PromiseLookup.cpp
@@ -0,0 +1,272 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "vm/PromiseLookup.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include "builtin/Promise.h" // js::Promise_then, js::Promise_static_resolve, js::Promise_static_species
+#include "js/HeapAPI.h" // js::gc::IsInsideNursery
+#include "js/Id.h" // SYMBOL_TO_JSID
+#include "js/ProtoKey.h" // JSProto_*
+#include "js/Value.h" // JS::Value, JS::ObjectValue
+#include "util/Poison.h" // js::AlwaysPoison, JS_RESET_VALUE_PATTERN, MemCheckKind
+#include "vm/GlobalObject.h" // js::GlobalObject
+#include "vm/JSContext.h" // JSContext
+#include "vm/JSFunction.h" // JSFunction
+#include "vm/JSObject.h" // JSObject
+#include "vm/NativeObject.h" // js::NativeObject
+#include "vm/Runtime.h" // js::WellKnownSymbols
+#include "vm/Shape.h" // js::Shape
+
+#include "vm/JSObject-inl.h" // js::IsFunctionObject, js::IsNativeFunction
+
+using JS::ObjectValue;
+using JS::Value;
+
+using js::NativeObject;
+
+JSFunction* js::PromiseLookup::getPromiseConstructor(JSContext* cx) {
+ const Value& val = cx->global()->getConstructor(JSProto_Promise);
+ return val.isObject() ? &val.toObject().as<JSFunction>() : nullptr;
+}
+
+NativeObject* js::PromiseLookup::getPromisePrototype(JSContext* cx) {
+ const Value& val = cx->global()->getPrototype(JSProto_Promise);
+ return val.isObject() ? &val.toObject().as<NativeObject>() : nullptr;
+}
+
+bool js::PromiseLookup::isDataPropertyNative(JSContext* cx, NativeObject* obj,
+ uint32_t slot, JSNative native) {
+ JSFunction* fun;
+ if (!IsFunctionObject(obj->getSlot(slot), &fun)) {
+ return false;
+ }
+ return fun->maybeNative() == native && fun->realm() == cx->realm();
+}
+
+bool js::PromiseLookup::isAccessorPropertyNative(JSContext* cx, Shape* shape,
+ JSNative native) {
+ JSObject* getter = shape->getterObject();
+ return getter && IsNativeFunction(getter, native) &&
+ getter->as<JSFunction>().realm() == cx->realm();
+}
+
+void js::PromiseLookup::initialize(JSContext* cx) {
+ MOZ_ASSERT(state_ == State::Uninitialized);
+
+ // Get the canonical Promise.prototype.
+ NativeObject* promiseProto = getPromisePrototype(cx);
+
+ // Check condition 1:
+ // Leave the cache uninitialized if the Promise class itself is not yet
+ // initialized.
+ if (!promiseProto) {
+ return;
+ }
+
+ // Get the canonical Promise constructor.
+ JSFunction* promiseCtor = getPromiseConstructor(cx);
+ MOZ_ASSERT(promiseCtor,
+ "The Promise constructor is initialized iff Promise.prototype is "
+ "initialized");
+
+ // Shortcut returns below means Promise[@@species] will never be
+ // optimizable, set to disabled now, and clear it later when we succeed.
+ state_ = State::Disabled;
+
+ // Check condition 2:
+ // Look up Promise.prototype.constructor and ensure it's a data property.
+ Shape* ctorShape = promiseProto->lookup(cx, cx->names().constructor);
+ if (!ctorShape || !ctorShape->isDataProperty()) {
+ return;
+ }
+
+ // Get the referred value, and ensure it holds the canonical Promise
+ // constructor.
+ JSFunction* ctorFun;
+ if (!IsFunctionObject(promiseProto->getSlot(ctorShape->slot()), &ctorFun)) {
+ return;
+ }
+ if (ctorFun != promiseCtor) {
+ return;
+ }
+
+ // Check condition 3:
+ // Look up Promise.prototype.then and ensure it's a data property.
+ Shape* thenShape = promiseProto->lookup(cx, cx->names().then);
+ if (!thenShape || !thenShape->isDataProperty()) {
+ return;
+ }
+
+ // Get the referred value, and ensure it holds the canonical "then"
+ // function.
+ if (!isDataPropertyNative(cx, promiseProto, thenShape->slot(),
+ Promise_then)) {
+ return;
+ }
+
+ // Check condition 4:
+ // Look up the '@@species' value on Promise.
+ Shape* speciesShape =
+ promiseCtor->lookup(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().species));
+ if (!speciesShape || !speciesShape->hasGetterObject()) {
+ return;
+ }
+
+ // Get the referred value, ensure it holds the canonical Promise[@@species]
+ // function.
+ if (!isAccessorPropertyNative(cx, speciesShape, Promise_static_species)) {
+ return;
+ }
+
+ // Check condition 5:
+ // Look up Promise.resolve and ensure it's a data property.
+ Shape* resolveShape = promiseCtor->lookup(cx, cx->names().resolve);
+ if (!resolveShape || !resolveShape->isDataProperty()) {
+ return;
+ }
+
+ // Get the referred value, and ensure it holds the canonical "resolve"
+ // function.
+ if (!isDataPropertyNative(cx, promiseCtor, resolveShape->slot(),
+ Promise_static_resolve)) {
+ return;
+ }
+
+ // Store raw pointers below. This is okay to do here, because all objects
+ // are in the tenured heap.
+ MOZ_ASSERT(!gc::IsInsideNursery(promiseCtor->lastProperty()));
+ MOZ_ASSERT(!gc::IsInsideNursery(speciesShape));
+ MOZ_ASSERT(!gc::IsInsideNursery(promiseProto->lastProperty()));
+
+ state_ = State::Initialized;
+ promiseConstructorShape_ = promiseCtor->lastProperty();
+#ifdef DEBUG
+ promiseSpeciesShape_ = speciesShape;
+#endif
+ promiseProtoShape_ = promiseProto->lastProperty();
+ promiseResolveSlot_ = resolveShape->slot();
+ promiseProtoConstructorSlot_ = ctorShape->slot();
+ promiseProtoThenSlot_ = thenShape->slot();
+}
+
+void js::PromiseLookup::reset() {
+ AlwaysPoison(this, JS_RESET_VALUE_PATTERN, sizeof(*this),
+ MemCheckKind::MakeUndefined);
+ state_ = State::Uninitialized;
+}
+
+bool js::PromiseLookup::isPromiseStateStillSane(JSContext* cx) {
+ MOZ_ASSERT(state_ == State::Initialized);
+
+ NativeObject* promiseProto = getPromisePrototype(cx);
+ MOZ_ASSERT(promiseProto);
+
+ NativeObject* promiseCtor = getPromiseConstructor(cx);
+ MOZ_ASSERT(promiseCtor);
+
+ // Ensure that Promise.prototype still has the expected shape.
+ if (promiseProto->lastProperty() != promiseProtoShape_) {
+ return false;
+ }
+
+ // Ensure that Promise still has the expected shape.
+ if (promiseCtor->lastProperty() != promiseConstructorShape_) {
+ return false;
+ }
+
+ // Ensure that Promise.prototype.constructor is the canonical constructor.
+ if (promiseProto->getSlot(promiseProtoConstructorSlot_) !=
+ ObjectValue(*promiseCtor)) {
+ return false;
+ }
+
+ // Ensure that Promise.prototype.then is the canonical "then" function.
+ if (!isDataPropertyNative(cx, promiseProto, promiseProtoThenSlot_,
+ Promise_then)) {
+ return false;
+ }
+
+ // Ensure the species getter contains the canonical @@species function.
+ // Note: This is currently guaranteed to be always true, because modifying
+ // the getter property implies a new shape is generated. If this ever
+ // changes, convert this assertion into an if-statement.
+#ifdef DEBUG
+ MOZ_ASSERT(isAccessorPropertyNative(cx, promiseSpeciesShape_,
+ Promise_static_species));
+#endif
+
+ // Ensure that Promise.resolve is the canonical "resolve" function.
+ if (!isDataPropertyNative(cx, promiseCtor, promiseResolveSlot_,
+ Promise_static_resolve)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool js::PromiseLookup::ensureInitialized(JSContext* cx,
+ Reinitialize reinitialize) {
+ if (state_ == State::Uninitialized) {
+ // If the cache is not initialized, initialize it.
+ initialize(cx);
+ } else if (state_ == State::Initialized) {
+ if (reinitialize == Reinitialize::Allowed) {
+ if (!isPromiseStateStillSane(cx)) {
+ // If the promise state is no longer sane, reinitialize.
+ reset();
+ initialize(cx);
+ }
+ } else {
+ // When we're not allowed to reinitialize, the promise state must
+ // still be sane if the cache is already initialized.
+ MOZ_ASSERT(isPromiseStateStillSane(cx));
+ }
+ }
+
+ // If the cache is disabled or still uninitialized, don't bother trying to
+ // optimize.
+ if (state_ != State::Initialized) {
+ return false;
+ }
+
+ // By the time we get here, we should have a sane promise state.
+ MOZ_ASSERT(isPromiseStateStillSane(cx));
+
+ return true;
+}
+
+bool js::PromiseLookup::isDefaultPromiseState(JSContext* cx) {
+ // Promise and Promise.prototype are in their default states iff the
+ // lookup cache was successfully initialized.
+ return ensureInitialized(cx, Reinitialize::Allowed);
+}
+
+bool js::PromiseLookup::hasDefaultProtoAndNoShadowedProperties(
+ JSContext* cx, PromiseObject* promise) {
+ // Ensure |promise|'s prototype is the actual Promise.prototype.
+ if (promise->staticPrototype() != getPromisePrototype(cx)) {
+ return false;
+ }
+
+ // Ensure |promise| doesn't define any own properties. This serves as a
+ // quick check to make sure |promise| doesn't define an own "constructor"
+ // or "then" property which may shadow Promise.prototype.constructor or
+ // Promise.prototype.then.
+ return promise->lastProperty()->isEmptyShape();
+}
+
+bool js::PromiseLookup::isDefaultInstance(JSContext* cx, PromiseObject* promise,
+ Reinitialize reinitialize) {
+ // Promise and Promise.prototype must be in their default states.
+ if (!ensureInitialized(cx, reinitialize)) {
+ return false;
+ }
+
+ // The object uses the default properties from Promise.prototype.
+ return hasDefaultProtoAndNoShadowedProperties(cx, promise);
+}