/* -*- 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() : nullptr; } NativeObject* js::PromiseLookup::getPromisePrototype(JSContext* cx) { const Value& val = cx->global()->getPrototype(JSProto_Promise); return val.isObject() ? &val.toObject().as() : 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().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); }