diff --git a/js/src/builtin/Array.cpp b/js/src/builtin/Array.cpp --- a/js/src/builtin/Array.cpp +++ b/js/src/builtin/Array.cpp @@ -1,4261 +1,4261 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 "builtin/Array-inl.h" #include "mozilla/ArrayUtils.h" #include "mozilla/CheckedInt.h" #include "mozilla/DebugOnly.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/TextUtils.h" #include #include "jsapi.h" #include "jsfriendapi.h" #include "jsnum.h" #include "jstypes.h" #include "jsutil.h" #include "ds/Sort.h" #include "gc/Heap.h" #include "jit/InlinableNatives.h" #include "js/Class.h" #include "js/Conversions.h" #include "util/StringBuffer.h" #include "util/Text.h" #include "vm/ArgumentsObject.h" #include "vm/Interpreter.h" #include "vm/Iteration.h" #include "vm/JSAtom.h" #include "vm/JSContext.h" #include "vm/JSFunction.h" #include "vm/JSObject.h" #include "vm/SelfHosting.h" #include "vm/Shape.h" #include "vm/TypedArrayObject.h" #include "vm/WrapperObject.h" #include "vm/ArgumentsObject-inl.h" #include "vm/ArrayObject-inl.h" #include "vm/Caches-inl.h" #include "vm/GeckoProfiler-inl.h" #include "vm/Interpreter-inl.h" #include "vm/JSAtom-inl.h" #include "vm/NativeObject-inl.h" #include "vm/UnboxedObject-inl.h" using namespace js; using namespace js::gc; using mozilla::Abs; using mozilla::ArrayLength; using mozilla::CeilingLog2; using mozilla::CheckedInt; using mozilla::DebugOnly; using mozilla::IsAsciiDigit; using JS::AutoCheckCannotGC; using JS::IsArrayAnswer; using JS::ToUint32; bool JS::IsArray(JSContext* cx, HandleObject obj, IsArrayAnswer* answer) { if (obj->is()) { *answer = IsArrayAnswer::Array; return true; } if (obj->is()) return Proxy::isArray(cx, obj, answer); *answer = IsArrayAnswer::NotArray; return true; } bool JS::IsArray(JSContext* cx, HandleObject obj, bool* isArray) { IsArrayAnswer answer; if (!IsArray(cx, obj, &answer)) return false; if (answer == IsArrayAnswer::RevokedProxy) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED); return false; } *isArray = answer == IsArrayAnswer::Array; return true; } // ES2017 7.1.15 ToLength, but clamped to the [0,2^32-2] range. static bool ToLengthClamped(JSContext* cx, HandleValue v, uint32_t* out) { if (v.isInt32()) { int32_t i = v.toInt32(); *out = i < 0 ? 0 : i; return true; } double d; if (v.isDouble()) { d = v.toDouble(); } else { if (!ToNumber(cx, v, &d)) return false; } d = JS::ToInteger(d); if (d <= 0.0) *out = 0; else if (d < double(UINT32_MAX - 1)) *out = uint32_t(d); else *out = UINT32_MAX; return true; } bool js::GetLengthProperty(JSContext* cx, HandleObject obj, uint32_t* lengthp) { if (obj->is()) { *lengthp = obj->as().length(); return true; } if (obj->is()) { ArgumentsObject& argsobj = obj->as(); if (!argsobj.hasOverriddenLength()) { *lengthp = argsobj.initialLength(); return true; } } RootedValue value(cx); if (!GetProperty(cx, obj, obj, cx->names().length, &value)) return false; if (!ToLengthClamped(cx, value, lengthp)) return false; return true; } // ES2017 7.1.15 ToLength. static bool ToLength(JSContext* cx, HandleValue v, uint64_t* out) { if (v.isInt32()) { int32_t i = v.toInt32(); *out = i < 0 ? 0 : i; return true; } double d; if (v.isDouble()) { d = v.toDouble(); } else { if (!ToNumber(cx, v, &d)) return false; } d = JS::ToInteger(d); if (d <= 0.0) *out = 0; else *out = uint64_t(Min(d, DOUBLE_INTEGRAL_PRECISION_LIMIT - 1)); return true; } static MOZ_ALWAYS_INLINE bool GetLengthProperty(JSContext* cx, HandleObject obj, uint64_t* lengthp) { if (obj->is()) { *lengthp = obj->as().length(); return true; } if (obj->is()) { ArgumentsObject& argsobj = obj->as(); if (!argsobj.hasOverriddenLength()) { *lengthp = argsobj.initialLength(); return true; } } RootedValue value(cx); if (!GetProperty(cx, obj, obj, cx->names().length, &value)) return false; return ToLength(cx, value, lengthp); } /* * Determine if the id represents an array index. * * An id is an array index according to ECMA by (15.4): * * "Array objects give special treatment to a certain class of property names. * A property name P (in the form of a string value) is an array index if and * only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal * to 2^32-1." * * This means the largest allowed index is actually 2^32-2 (4294967294). * * In our implementation, it would be sufficient to check for id.isInt32() * except that by using signed 31-bit integers we miss the top half of the * valid range. This function checks the string representation itself; note * that calling a standard conversion routine might allow strings such as * "08" or "4.0" as array indices, which they are not. * */ template static bool StringIsArrayIndex(const CharT* s, uint32_t length, uint32_t* indexp) { const CharT* end = s + length; if (length == 0 || length > (sizeof("4294967294") - 1) || !IsAsciiDigit(*s)) return false; uint32_t c = 0, previous = 0; uint32_t index = JS7_UNDEC(*s++); /* Don't allow leading zeros. */ if (index == 0 && s != end) return false; for (; s < end; s++) { if (!IsAsciiDigit(*s)) return false; previous = index; c = JS7_UNDEC(*s); index = 10 * index + c; } /* Make sure we didn't overflow. */ if (previous < (MAX_ARRAY_INDEX / 10) || (previous == (MAX_ARRAY_INDEX / 10) && c <= (MAX_ARRAY_INDEX % 10))) { MOZ_ASSERT(index <= MAX_ARRAY_INDEX); *indexp = index; return true; } return false; } JS_FRIEND_API(bool) js::StringIsArrayIndex(JSLinearString* str, uint32_t* indexp) { AutoCheckCannotGC nogc; return str->hasLatin1Chars() ? ::StringIsArrayIndex(str->latin1Chars(nogc), str->length(), indexp) : ::StringIsArrayIndex(str->twoByteChars(nogc), str->length(), indexp); } template static bool ToId(JSContext* cx, T index, MutableHandleId id); template <> bool ToId(JSContext* cx, uint32_t index, MutableHandleId id) { return IndexToId(cx, index, id); } template <> bool ToId(JSContext* cx, uint64_t index, MutableHandleId id) { MOZ_ASSERT(index < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)); if (index == uint32_t(index)) return IndexToId(cx, uint32_t(index), id); Value tmp = DoubleValue(index); return ValueToId(cx, HandleValue::fromMarkedLocation(&tmp), id); } /* * If the property at the given index exists, get its value into |vp| and set * |*hole| to false. Otherwise set |*hole| to true and |vp| to Undefined. */ template static bool HasAndGetElement(JSContext* cx, HandleObject obj, HandleObject receiver, T index, bool* hole, MutableHandleValue vp) { if (obj->isNative()) { NativeObject* nobj = &obj->as(); if (index < nobj->getDenseInitializedLength()) { vp.set(nobj->getDenseElement(size_t(index))); if (!vp.isMagic(JS_ELEMENTS_HOLE)) { *hole = false; return true; } } if (nobj->is() && index <= UINT32_MAX) { if (nobj->as().maybeGetElement(uint32_t(index), vp)) { *hole = false; return true; } } } RootedId id(cx); if (!ToId(cx, index, &id)) return false; bool found; if (!HasProperty(cx, obj, id, &found)) return false; if (found) { if (!GetProperty(cx, obj, receiver, id, vp)) return false; } else { vp.setUndefined(); } *hole = !found; return true; } template static inline bool HasAndGetElement(JSContext* cx, HandleObject obj, T index, bool* hole, MutableHandleValue vp) { return HasAndGetElement(cx, obj, obj, index, hole, vp); } bool ElementAdder::append(JSContext* cx, HandleValue v) { MOZ_ASSERT(index_ < length_); if (resObj_) { NativeObject* resObj = &resObj_->as(); DenseElementResult result = resObj->setOrExtendDenseElements(cx, index_, v.address(), 1); if (result == DenseElementResult::Failure) return false; if (result == DenseElementResult::Incomplete) { if (!DefineDataElement(cx, resObj_, index_, v)) return false; } } else { vp_[index_] = v; } index_++; return true; } void ElementAdder::appendHole() { MOZ_ASSERT(getBehavior_ == ElementAdder::CheckHasElemPreserveHoles); MOZ_ASSERT(index_ < length_); if (!resObj_) vp_[index_].setMagic(JS_ELEMENTS_HOLE); index_++; } bool js::GetElementsWithAdder(JSContext* cx, HandleObject obj, HandleObject receiver, uint32_t begin, uint32_t end, ElementAdder* adder) { MOZ_ASSERT(begin <= end); RootedValue val(cx); for (uint32_t i = begin; i < end; i++) { if (adder->getBehavior() == ElementAdder::CheckHasElemPreserveHoles) { bool hole; if (!HasAndGetElement(cx, obj, receiver, i, &hole, &val)) return false; if (hole) { adder->appendHole(); continue; } } else { MOZ_ASSERT(adder->getBehavior() == ElementAdder::GetElement); if (!GetElement(cx, obj, receiver, i, &val)) return false; } if (!adder->append(cx, val)) return false; } return true; } static bool ObjectMayHaveExtraIndexedProperties(JSObject* obj); static inline bool IsPackedArrayOrNoExtraIndexedProperties(JSObject* obj, uint64_t length) { return (IsPackedArray(obj) && obj->as().length() == length) || !ObjectMayHaveExtraIndexedProperties(obj); } static bool GetDenseElements(NativeObject* aobj, uint32_t length, Value* vp) { MOZ_ASSERT(IsPackedArrayOrNoExtraIndexedProperties(aobj, length)); if (length > aobj->getDenseInitializedLength()) return false; for (size_t i = 0; i < length; i++) { vp[i] = aobj->getDenseElement(i); // No other indexed properties so hole => undefined. if (vp[i].isMagic(JS_ELEMENTS_HOLE)) vp[i] = UndefinedValue(); } return true; } bool js::GetElements(JSContext* cx, HandleObject aobj, uint32_t length, Value* vp) { if (IsPackedArrayOrNoExtraIndexedProperties(aobj, length)) { if (GetDenseElements(&aobj->as(), length, vp)) return true; } if (aobj->is()) { ArgumentsObject& argsobj = aobj->as(); if (!argsobj.hasOverriddenLength()) { if (argsobj.maybeGetElements(0, length, vp)) return true; } } if (aobj->is()) { TypedArrayObject* typedArray = &aobj->as(); if (typedArray->length() == length) { typedArray->getElements(vp); return true; } } if (js::GetElementsOp op = aobj->getOpsGetElements()) { ElementAdder adder(cx, vp, length, ElementAdder::GetElement); return op(cx, aobj, 0, length, &adder); } for (uint32_t i = 0; i < length; i++) { if (!GetElement(cx, aobj, aobj, i, MutableHandleValue::fromMarkedLocation(&vp[i]))) return false; } return true; } static inline bool GetArrayElement(JSContext* cx, HandleObject obj, uint64_t index, MutableHandleValue vp) { if (obj->isNative()) { NativeObject* nobj = &obj->as(); if (index < nobj->getDenseInitializedLength()) { vp.set(nobj->getDenseElement(size_t(index))); if (!vp.isMagic(JS_ELEMENTS_HOLE)) return true; } if (nobj->is() && index <= UINT32_MAX) { if (nobj->as().maybeGetElement(uint32_t(index), vp)) return true; } } RootedId id(cx); if (!ToId(cx, index, &id)) return false; return GetProperty(cx, obj, obj, id, vp); } static inline bool DefineArrayElement(JSContext* cx, HandleObject obj, uint64_t index, HandleValue value) { RootedId id(cx); if (!ToId(cx, index, &id)) return false; return DefineDataProperty(cx, obj, id, value); } // Set the value of the property at the given index to v. static inline bool SetArrayElement(JSContext* cx, HandleObject obj, uint64_t index, HandleValue v) { RootedId id(cx); if (!ToId(cx, index, &id)) return false; return SetProperty(cx, obj, id, v); } /* * Attempt to delete the element |index| from |obj| as if by * |obj.[[Delete]](index)|. * * If an error occurs while attempting to delete the element (that is, the call * to [[Delete]] threw), return false. * * Otherwise call result.succeed() or result.fail() to indicate whether the * deletion attempt succeeded (that is, whether the call to [[Delete]] returned * true or false). (Deletes generally fail only when the property is * non-configurable, but proxies may implement different semantics.) */ static bool DeleteArrayElement(JSContext* cx, HandleObject obj, uint64_t index, ObjectOpResult& result) { if (obj->is() && !obj->as().isIndexed() && !obj->as().denseElementsAreSealed()) { ArrayObject* aobj = &obj->as(); if (index <= UINT32_MAX) { uint32_t idx = uint32_t(index); if (idx < aobj->getDenseInitializedLength()) { if (!aobj->maybeCopyElementsForWrite(cx)) return false; if (idx+1 == aobj->getDenseInitializedLength()) { aobj->setDenseInitializedLengthMaybeNonExtensible(cx, idx); } else { aobj->markDenseElementsNotPacked(cx); aobj->setDenseElement(idx, MagicValue(JS_ELEMENTS_HOLE)); } if (!SuppressDeletedElement(cx, obj, idx)) return false; } } return result.succeed(); } RootedId id(cx); if (!ToId(cx, index, &id)) return false; return DeleteProperty(cx, obj, id, result); } /* ES6 draft rev 32 (2 Febr 2015) 7.3.7 */ static bool DeletePropertyOrThrow(JSContext* cx, HandleObject obj, uint64_t index) { ObjectOpResult success; if (!DeleteArrayElement(cx, obj, index, success)) return false; if (!success) { RootedId id(cx); if (!ToId(cx, index, &id)) return false; return success.reportError(cx, obj, id); } return true; } static bool DeletePropertiesOrThrow(JSContext* cx, HandleObject obj, uint64_t len, uint64_t finalLength) { if (obj->is() && !obj->as().isIndexed() && !obj->as().denseElementsAreSealed()) { if (len <= UINT32_MAX) { // Skip forward to the initialized elements of this array. len = Min(uint32_t(len), obj->as().getDenseInitializedLength()); } } for (uint64_t k = len; k > finalLength; k--) { if (!CheckForInterrupt(cx)) return false; if (!DeletePropertyOrThrow(cx, obj, k - 1)) return false; } return true; } static bool SetArrayLengthProperty(JSContext* cx, HandleArrayObject obj, HandleValue value) { RootedId id(cx, NameToId(cx->names().length)); ObjectOpResult result; if (obj->lengthIsWritable()) { if (!ArraySetLength(cx, obj, id, JSPROP_PERMANENT, value, result)) return false; } else { MOZ_ALWAYS_TRUE(result.fail(JSMSG_READ_ONLY)); } return result.checkStrict(cx, obj, id); } static bool SetLengthProperty(JSContext* cx, HandleObject obj, uint64_t length) { MOZ_ASSERT(length < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)); RootedValue v(cx, NumberValue(length)); if (obj->is()) return SetArrayLengthProperty(cx, obj.as(), v); return SetProperty(cx, obj, cx->names().length, v); } bool js::SetLengthProperty(JSContext* cx, HandleObject obj, uint32_t length) { RootedValue v(cx, NumberValue(length)); if (obj->is()) return SetArrayLengthProperty(cx, obj.as(), v); return SetProperty(cx, obj, cx->names().length, v); } static bool array_length_getter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp) { vp.setNumber(obj->as().length()); return true; } static bool array_length_setter(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, ObjectOpResult& result) { MOZ_ASSERT(id == NameToId(cx->names().length)); if (!obj->is()) { // This array .length property was found on the prototype // chain. Ideally the setter should not have been called, but since // we're here, do an impression of SetPropertyByDefining. return DefineDataProperty(cx, obj, id, v, JSPROP_ENUMERATE, result); } HandleArrayObject arr = obj.as(); MOZ_ASSERT(arr->lengthIsWritable(), "setter shouldn't be called if property is non-writable"); return ArraySetLength(cx, arr, id, JSPROP_PERMANENT, v, result); } struct ReverseIndexComparator { bool operator()(const uint32_t& a, const uint32_t& b, bool* lessOrEqualp) { MOZ_ASSERT(a != b, "how'd we get duplicate indexes?"); *lessOrEqualp = b <= a; return true; } }; static bool MaybeInIteration(HandleObject obj, JSContext* cx) { /* * Don't optimize if the array might be in the midst of iteration. We * rely on this to be able to safely move dense array elements around with * just a memmove (see NativeObject::moveDenseArrayElements), without worrying * about updating any in-progress enumerators for properties implicitly * deleted if a hole is moved from one location to another location not yet * visited. See bug 690622. * * Note that it's fine to optimize if |obj| is on the prototype of another * object: SuppressDeletedProperty only suppresses properties deleted from * the iterated object itself. */ if (MOZ_LIKELY(!ObjectRealm::get(obj).objectMaybeInIteration(obj))) return false; ObjectGroup* group = JSObject::getGroup(cx, obj); if (MOZ_UNLIKELY(!group)) { cx->recoverFromOutOfMemory(); return true; } AutoSweepObjectGroup sweep(group); if (MOZ_UNLIKELY(group->hasAllFlags(sweep, OBJECT_FLAG_ITERATED))) return true; return false; } /* ES6 draft rev 34 (2015 Feb 20) 9.4.2.4 ArraySetLength */ bool js::ArraySetLength(JSContext* cx, Handle arr, HandleId id, unsigned attrs, HandleValue value, ObjectOpResult& result) { MOZ_ASSERT(id == NameToId(cx->names().length)); if (!arr->maybeCopyElementsForWrite(cx)) return false; // Step 1. uint32_t newLen; if (attrs & JSPROP_IGNORE_VALUE) { MOZ_ASSERT(value.isUndefined()); // The spec has us calling OrdinaryDefineOwnProperty if // Desc.[[Value]] is absent, but our implementation is so different that // this is impossible. Instead, set newLen to the current length and // proceed to step 9. newLen = arr->length(); } else { // Step 2 is irrelevant in our implementation. // Step 3. if (!ToUint32(cx, value, &newLen)) return false; // Step 4. double d; if (!ToNumber(cx, value, &d)) return false; // Step 5. if (d != newLen) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); return false; } // Steps 6-8 are irrelevant in our implementation. } // Steps 9-11. bool lengthIsWritable = arr->lengthIsWritable(); #ifdef DEBUG { RootedShape lengthShape(cx, arr->lookupPure(id)); MOZ_ASSERT(lengthShape); MOZ_ASSERT(lengthShape->writable() == lengthIsWritable); } #endif uint32_t oldLen = arr->length(); // Part of steps 1.a, 12.a, and 16: Fail if we're being asked to change // enumerability or configurability, or otherwise break the object // invariants. (ES6 checks these by calling OrdinaryDefineOwnProperty, but // in SM, the array length property is hardly ordinary.) if ((attrs & (JSPROP_PERMANENT | JSPROP_IGNORE_PERMANENT)) == 0 || (attrs & (JSPROP_ENUMERATE | JSPROP_IGNORE_ENUMERATE)) == JSPROP_ENUMERATE || (attrs & (JSPROP_GETTER | JSPROP_SETTER)) != 0 || (!lengthIsWritable && (attrs & (JSPROP_READONLY | JSPROP_IGNORE_READONLY)) == 0)) { return result.fail(JSMSG_CANT_REDEFINE_PROP); } // Steps 12-13 for arrays with non-writable length. if (!lengthIsWritable) { if (newLen == oldLen) return result.succeed(); return result.fail(JSMSG_CANT_REDEFINE_ARRAY_LENGTH); } // Step 19. bool succeeded = true; do { // The initialized length and capacity of an array only need updating // when non-hole elements are added or removed, which doesn't happen // when array length stays the same or increases. if (newLen >= oldLen) break; // Attempt to propagate dense-element optimization tricks, if possible, // and avoid the generic (and accordingly slow) deletion code below. // We can only do this if there are only densely-indexed elements. // Once there's a sparse indexed element, there's no good way to know, // save by enumerating all the properties to find it. But we *have* to // know in case that sparse indexed element is non-configurable, as // that element must prevent any deletions below it. Bug 586842 should // fix this inefficiency by moving indexed storage to be entirely // separate from non-indexed storage. // A second reason for this optimization to be invalid is an active // for..in iteration over the array. Keys deleted before being reached // during the iteration must not be visited, and suppressing them here // would be too costly. // This optimization is also invalid when there are sealed // (non-configurable) elements. if (!arr->isIndexed() && !MaybeInIteration(arr, cx) && !arr->denseElementsAreSealed()) { if (!arr->maybeCopyElementsForWrite(cx)) return false; uint32_t oldCapacity = arr->getDenseCapacity(); uint32_t oldInitializedLength = arr->getDenseInitializedLength(); MOZ_ASSERT(oldCapacity >= oldInitializedLength); if (oldInitializedLength > newLen) arr->setDenseInitializedLengthMaybeNonExtensible(cx, newLen); if (oldCapacity > newLen) { if (arr->isExtensible()) arr->shrinkElements(cx, newLen); else MOZ_ASSERT(arr->getDenseInitializedLength() == arr->getDenseCapacity()); } // We've done the work of deleting any dense elements needing // deletion, and there are no sparse elements. Thus we can skip // straight to defining the length. break; } // Step 15. // // Attempt to delete all elements above the new length, from greatest // to least. If any of these deletions fails, we're supposed to define // the length to one greater than the index that couldn't be deleted, // *with the property attributes specified*. This might convert the // length to be not the value specified, yet non-writable. (You may be // forgiven for thinking these are interesting semantics.) Example: // // var arr = // Object.defineProperty([0, 1, 2, 3], 1, { writable: false }); // Object.defineProperty(arr, "length", // { value: 0, writable: false }); // // will convert |arr| to an array of non-writable length two, then // throw a TypeError. // // We implement this behavior, in the relevant lops below, by setting // |succeeded| to false. Then we exit the loop, define the length // appropriately, and only then throw a TypeError, if necessary. uint32_t gap = oldLen - newLen; const uint32_t RemoveElementsFastLimit = 1 << 24; if (gap < RemoveElementsFastLimit) { // If we're removing a relatively small number of elements, just do // it exactly by the spec. while (newLen < oldLen) { // Step 15a. oldLen--; // Steps 15b-d. ObjectOpResult deleteSucceeded; if (!DeleteElement(cx, arr, oldLen, deleteSucceeded)) return false; if (!deleteSucceeded) { newLen = oldLen + 1; succeeded = false; break; } } } else { // If we're removing a large number of elements from an array // that's probably sparse, try a different tack. Get all the own // property names, sift out the indexes in the deletion range into // a vector, sort the vector greatest to least, then delete the // indexes greatest to least using that vector. See bug 322135. // // This heuristic's kind of a huge guess -- "large number of // elements" and "probably sparse" are completely unprincipled // predictions. In the long run, bug 586842 will support the right // fix: store sparse elements in a sorted data structure that // permits fast in-reverse-order traversal and concurrent removals. Vector indexes(cx); { AutoIdVector props(cx); if (!GetPropertyKeys(cx, arr, JSITER_OWNONLY | JSITER_HIDDEN, &props)) return false; for (size_t i = 0; i < props.length(); i++) { if (!CheckForInterrupt(cx)) return false; uint32_t index; if (!IdIsIndex(props[i], &index)) continue; if (index >= newLen && index < oldLen) { if (!indexes.append(index)) return false; } } } uint32_t count = indexes.length(); { // We should use radix sort to be O(n), but this is uncommon // enough that we'll punt til someone complains. Vector scratch(cx); if (!scratch.resize(count)) return false; MOZ_ALWAYS_TRUE(MergeSort(indexes.begin(), count, scratch.begin(), ReverseIndexComparator())); } uint32_t index = UINT32_MAX; for (uint32_t i = 0; i < count; i++) { MOZ_ASSERT(indexes[i] < index, "indexes should never repeat"); index = indexes[i]; // Steps 15b-d. ObjectOpResult deleteSucceeded; if (!DeleteElement(cx, arr, index, deleteSucceeded)) return false; if (!deleteSucceeded) { newLen = index + 1; succeeded = false; break; } } } } while (false); // Update array length. Technically we should have been doing this // throughout the loop, in step 19.d.iii. arr->setLength(cx, newLen); // Step 20. if (attrs & JSPROP_READONLY) { // Yes, we totally drop a non-stub getter/setter from a defineProperty // API call on the floor here. Given that getter/setter will go away in // the long run, with accessors replacing them both internally and at the // API level, just run with this. RootedShape lengthShape(cx, arr->lookup(cx, id)); if (!NativeObject::changeProperty(cx, arr, lengthShape, lengthShape->attributes() | JSPROP_READONLY, array_length_getter, array_length_setter)) { return false; } } // All operations past here until the |!succeeded| code must be infallible, // so that all element fields remain properly synchronized. // Trim the initialized length, if needed, to preserve the <= length // invariant. (Capacity was already reduced during element deletion, if // necessary.) ObjectElements* header = arr->getElementsHeader(); header->initializedLength = Min(header->initializedLength, newLen); if (!arr->isExtensible()) arr->shrinkCapacityToInitializedLength(cx); if (attrs & JSPROP_READONLY) arr->setNonWritableLength(cx); if (!succeeded) return result.fail(JSMSG_CANT_TRUNCATE_ARRAY); return result.succeed(); } static bool array_addProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v) { ArrayObject* arr = &obj->as(); uint32_t index; if (!IdIsIndex(id, &index)) return true; uint32_t length = arr->length(); if (index >= length) { MOZ_ASSERT(arr->lengthIsWritable(), "how'd this element get added if length is non-writable?"); arr->setLength(cx, index + 1); } return true; } static inline bool ObjectMayHaveExtraIndexedOwnProperties(JSObject* obj) { if (!obj->isNative()) return true; if (obj->as().isIndexed()) return true; if (obj->is()) return true; return ClassMayResolveId(*obj->runtimeFromAnyThread()->commonNames, obj->getClass(), INT_TO_JSID(0), obj); } /* * Whether obj may have indexed properties anywhere besides its dense * elements. This includes other indexed properties in its shape hierarchy, and * indexed properties or elements along its prototype chain. */ static bool ObjectMayHaveExtraIndexedProperties(JSObject* obj) { MOZ_ASSERT_IF(obj->hasDynamicPrototype(), !obj->isNative()); if (ObjectMayHaveExtraIndexedOwnProperties(obj)) return true; do { MOZ_ASSERT(obj->hasStaticPrototype(), "dynamic-prototype objects must be non-native, ergo must " "have failed ObjectMayHaveExtraIndexedOwnProperties"); obj = obj->staticPrototype(); if (!obj) return false; // no extra indexed properties found if (ObjectMayHaveExtraIndexedOwnProperties(obj)) return true; if (obj->as().getDenseInitializedLength() != 0) return true; } while (true); } static bool AddLengthProperty(JSContext* cx, HandleArrayObject obj) { // Add the 'length' property for a newly created array. Shapes are shared // across realms within a zone and because we update the initial shape with // a Shape that contains the length-property (in NewArray), it's possible // the length property has already been defined. Shape* shape = obj->lastProperty(); if (!shape->isEmptyShape()) { MOZ_ASSERT(JSID_IS_ATOM(shape->propidRaw(), cx->names().length)); MOZ_ASSERT(shape->previous()->isEmptyShape()); return true; } RootedId lengthId(cx, NameToId(cx->names().length)); return NativeObject::addAccessorProperty(cx, obj, lengthId, array_length_getter, array_length_setter, JSPROP_PERMANENT); } static bool IsArrayConstructor(const JSObject* obj) { // Note: this also returns true for cross-realm Array constructors in the // same compartment. return IsNativeFunction(obj, ArrayConstructor); } static bool IsArrayConstructor(const Value& v) { return v.isObject() && IsArrayConstructor(&v.toObject()); } bool js::IsCrossRealmArrayConstructor(JSContext* cx, const Value& v, bool* result) { if (!v.isObject()) { *result = false; return true; } JSObject* obj = &v.toObject(); if (obj->is()) { obj = CheckedUnwrap(obj); if (!obj) { ReportAccessDenied(cx); return false; } } *result = IsArrayConstructor(obj) && obj->as().realm() != cx->realm(); return true; } static MOZ_ALWAYS_INLINE bool IsArraySpecies(JSContext* cx, HandleObject origArray) { if (MOZ_UNLIKELY(origArray->is())) { if (origArray->getClass()->isDOMClass()) { #ifdef DEBUG // We assume DOM proxies never return true for IsArray. IsArrayAnswer answer; MOZ_ASSERT(Proxy::isArray(cx, origArray, &answer)); MOZ_ASSERT(answer == IsArrayAnswer::NotArray); #endif return true; } return false; } // 9.4.2.3 Step 4. Non-array objects always use the default constructor. if (!origArray->is()) return true; if (cx->realm()->arraySpeciesLookup.tryOptimizeArray(cx, &origArray->as())) return true; Value ctor; if (!GetPropertyPure(cx, origArray, NameToId(cx->names().constructor), &ctor)) return false; if (!IsArrayConstructor(ctor)) return ctor.isUndefined(); // 9.4.2.3 Step 6.c. Use the current realm's constructor if |ctor| is a // cross-realm Array constructor. if (cx->realm() != ctor.toObject().as().realm()) return true; jsid speciesId = SYMBOL_TO_JSID(cx->wellKnownSymbols().species); JSFunction* getter; if (!GetGetterPure(cx, &ctor.toObject(), speciesId, &getter)) return false; if (!getter) return false; return IsSelfHostedFunctionWithName(getter, cx->names().ArraySpecies); } static bool ArraySpeciesCreate(JSContext* cx, HandleObject origArray, uint64_t length, MutableHandleObject arr) { MOZ_ASSERT(length < DOUBLE_INTEGRAL_PRECISION_LIMIT); FixedInvokeArgs<2> args(cx); args[0].setObject(*origArray); args[1].set(NumberValue(length)); RootedValue rval(cx); if (!CallSelfHostedFunction(cx, cx->names().ArraySpeciesCreate, UndefinedHandleValue, args, &rval)) { return false; } MOZ_ASSERT(rval.isObject()); arr.set(&rval.toObject()); return true; } static bool array_toSource(JSContext* cx, unsigned argc, Value* vp) { if (!CheckRecursionLimit(cx)) return false; CallArgs args = CallArgsFromVp(argc, vp); if (!args.thisv().isObject()) { ReportIncompatible(cx, args); return false; } Rooted obj(cx, &args.thisv().toObject()); RootedValue elt(cx); AutoCycleDetector detector(cx, obj); if (!detector.init()) return false; StringBuffer sb(cx); if (detector.foundCycle()) { if (!sb.append("[]")) return false; goto make_string; } if (!sb.append('[')) return false; uint64_t length; if (!GetLengthProperty(cx, obj, &length)) return false; for (uint64_t index = 0; index < length; index++) { bool hole; if (!CheckForInterrupt(cx) || !HasAndGetElement(cx, obj, index, &hole, &elt)) { return false; } /* Get element's character string. */ JSString* str; if (hole) { str = cx->runtime()->emptyString; } else { str = ValueToSource(cx, elt); if (!str) return false; } /* Append element to buffer. */ if (!sb.append(str)) return false; if (index + 1 != length) { if (!sb.append(", ")) return false; } else if (hole) { if (!sb.append(',')) return false; } } /* Finalize the buffer. */ if (!sb.append(']')) return false; make_string: JSString* str = sb.finishString(); if (!str) return false; args.rval().setString(str); return true; } struct EmptySeparatorOp { bool operator()(JSContext*, StringBuffer& sb) { return true; } }; template struct CharSeparatorOp { const CharT sep; explicit CharSeparatorOp(CharT sep) : sep(sep) {} bool operator()(JSContext*, StringBuffer& sb) { return sb.append(sep); } }; struct StringSeparatorOp { HandleLinearString sep; explicit StringSeparatorOp(HandleLinearString sep) : sep(sep) {} bool operator()(JSContext* cx, StringBuffer& sb) { return sb.append(sep); } }; template static bool ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp, HandleNativeObject obj, uint64_t length, StringBuffer& sb, uint32_t* numProcessed) { // This loop handles all elements up to initializedLength. If // length > initLength we rely on the second loop to add the // other elements. MOZ_ASSERT(*numProcessed == 0); uint64_t initLength = Min(obj->getDenseInitializedLength(), length); MOZ_ASSERT(initLength <= UINT32_MAX, "initialized length shouldn't exceed UINT32_MAX"); uint32_t initLengthClamped = uint32_t(initLength); while (*numProcessed < initLengthClamped) { if (!CheckForInterrupt(cx)) return false; // Step 7.b. Value elem = obj->getDenseElement(*numProcessed); // Steps 7.c-d. if (elem.isString()) { if (!sb.append(elem.toString())) return false; } else if (elem.isNumber()) { if (!NumberValueToStringBuffer(cx, elem, sb)) return false; } else if (elem.isBoolean()) { if (!BooleanToStringBuffer(elem.toBoolean(), sb)) return false; } else if (elem.isObject() || elem.isSymbol()) { /* * Object stringifying could modify the initialized length or make * the array sparse. Delegate it to a separate loop to keep this * one tight. * * Symbol stringifying is a TypeError, so into the slow path * with those as well. */ break; } else if (IF_BIGINT(elem.isBigInt(), false)) { // ToString(bigint) doesn't access bigint.toString or // anything like that, so it can't mutate the array we're // walking through, so it *could* be handled here. We don't // do so yet for reasons of initial-implementation economy. break; } else { MOZ_ASSERT(elem.isMagic(JS_ELEMENTS_HOLE) || elem.isNullOrUndefined()); } // Steps 7.a, 7.e. if (++(*numProcessed) != length && !sepOp(cx, sb)) return false; } return true; } template static bool ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint64_t length, StringBuffer& sb) { // Step 6. uint32_t numProcessed = 0; if (IsPackedArrayOrNoExtraIndexedProperties(obj, length)) { if (!ArrayJoinDenseKernel(cx, sepOp, obj.as(), length, sb, &numProcessed)) { return false; } } // Step 7. if (numProcessed != length) { RootedValue v(cx); for (uint64_t i = numProcessed; i < length; ) { if (!CheckForInterrupt(cx)) return false; // Step 7.b. if (!GetArrayElement(cx, obj, i, &v)) return false; // Steps 7.c-d. if (!v.isNullOrUndefined()) { if (!ValueToStringBuffer(cx, v, sb)) return false; } // Steps 7.a, 7.e. if (++i != length && !sepOp(cx, sb)) return false; } } return true; } // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce // 22.1.3.13 Array.prototype.join ( separator ) bool js::array_join(JSContext* cx, unsigned argc, Value* vp) { if (!CheckRecursionLimit(cx)) return false; AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.join"); CallArgs args = CallArgsFromVp(argc, vp); // Step 1. RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; AutoCycleDetector detector(cx, obj); if (!detector.init()) return false; if (detector.foundCycle()) { args.rval().setString(cx->names().empty); return true; } // Step 2. uint64_t length; if (!GetLengthProperty(cx, obj, &length)) return false; // Steps 3-4. RootedLinearString sepstr(cx); if (args.hasDefined(0)) { JSString *s = ToString(cx, args[0]); if (!s) return false; sepstr = s->ensureLinear(cx); if (!sepstr) return false; } else { sepstr = cx->names().comma; } // Steps 5-8 (When the length is zero, directly return the empty string). if (length == 0) { args.rval().setString(cx->emptyString()); return true; } // An optimized version of a special case of steps 5-8: when length==1 and // the 0th element is a string, ToString() of that element is a no-op and // so it can be immediately returned as the result. if (length == 1 && obj->isNative()) { NativeObject* nobj = &obj->as(); if (nobj->getDenseInitializedLength() == 1) { Value elem0 = nobj->getDenseElement(0); if (elem0.isString()) { args.rval().set(elem0); return true; } } } // Step 5. StringBuffer sb(cx); if (sepstr->hasTwoByteChars() && !sb.ensureTwoByteChars()) return false; // The separator will be added |length - 1| times, reserve space for that // so that we don't have to unnecessarily grow the buffer. size_t seplen = sepstr->length(); if (seplen > 0) { if (length > UINT32_MAX) { ReportAllocationOverflow(cx); return false; } CheckedInt res = CheckedInt(seplen) * (uint32_t(length) - 1); if (!res.isValid()) { ReportAllocationOverflow(cx); return false; } if (!sb.reserve(res.value())) return false; } // Various optimized versions of steps 6-7. if (seplen == 0) { EmptySeparatorOp op; if (!ArrayJoinKernel(cx, op, obj, length, sb)) return false; } else if (seplen == 1) { char16_t c = sepstr->latin1OrTwoByteChar(0); if (c <= JSString::MAX_LATIN1_CHAR) { CharSeparatorOp op(c); if (!ArrayJoinKernel(cx, op, obj, length, sb)) return false; } else { CharSeparatorOp op(c); if (!ArrayJoinKernel(cx, op, obj, length, sb)) return false; } } else { StringSeparatorOp op(sepstr); if (!ArrayJoinKernel(cx, op, obj, length, sb)) return false; } // Step 8. JSString* str = sb.finishString(); if (!str) return false; args.rval().setString(str); return true; } // ES2017 draft rev f8a9be8ea4bd97237d176907a1e3080dce20c68f // 22.1.3.27 Array.prototype.toLocaleString ([ reserved1 [ , reserved2 ] ]) // ES2017 Intl draft rev 78bbe7d1095f5ff3760ac4017ed366026e4cb276 // 13.4.1 Array.prototype.toLocaleString ([ locales [ , options ]]) static bool array_toLocaleString(JSContext* cx, unsigned argc, Value* vp) { if (!CheckRecursionLimit(cx)) return false; CallArgs args = CallArgsFromVp(argc, vp); // Step 1 RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; // Avoid calling into self-hosted code if the array is empty. if (obj->is() && obj->as().length() == 0) { args.rval().setString(cx->names().empty); return true; } AutoCycleDetector detector(cx, obj); if (!detector.init()) return false; if (detector.foundCycle()) { args.rval().setString(cx->names().empty); return true; } FixedInvokeArgs<2> args2(cx); args2[0].set(args.get(0)); args2[1].set(args.get(1)); // Steps 2-10. RootedValue thisv(cx, ObjectValue(*obj)); return CallSelfHostedFunction(cx, cx->names().ArrayToLocaleString, thisv, args2, args.rval()); } /* vector must point to rooted memory. */ static bool SetArrayElements(JSContext* cx, HandleObject obj, uint64_t start, uint32_t count, const Value* vector, ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update) { MOZ_ASSERT(count <= MAX_ARRAY_INDEX); MOZ_ASSERT(start + count < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)); if (count == 0) return true; if (!ObjectMayHaveExtraIndexedProperties(obj) && start <= UINT32_MAX) { NativeObject* nobj = &obj->as(); DenseElementResult result = nobj->setOrExtendDenseElements(cx, uint32_t(start), vector, count, updateTypes); if (result != DenseElementResult::Incomplete) return result == DenseElementResult::Success; } RootedId id(cx); const Value* end = vector + count; while (vector < end) { if (!CheckForInterrupt(cx)) return false; if (!ToId(cx, start++, &id)) return false; if (!SetProperty(cx, obj, id, HandleValue::fromMarkedLocation(vector++))) return false; } return true; } static DenseElementResult ArrayReverseDenseKernel(JSContext* cx, HandleNativeObject obj, uint32_t length) { MOZ_ASSERT(length > 1); // If there are no elements, we're done. if (obj->getDenseInitializedLength() == 0) return DenseElementResult::Success; if (!obj->isExtensible()) return DenseElementResult::Incomplete; if (!IsPackedArray(obj)) { /* * It's actually surprisingly complicated to reverse an array due * to the orthogonality of array length and array capacity while * handling leading and trailing holes correctly. Reversing seems * less likely to be a common operation than other array * mass-mutation methods, so for now just take a probably-small * memory hit (in the absence of too many holes in the array at * its start) and ensure that the capacity is sufficient to hold * all the elements in the array if it were full. */ DenseElementResult result = obj->ensureDenseElements(cx, length, 0); if (result != DenseElementResult::Success) return result; /* Fill out the array's initialized length to its proper length. */ obj->ensureDenseInitializedLength(cx, length, 0); } else { if (!obj->maybeCopyElementsForWrite(cx)) return DenseElementResult::Failure; } if (!MaybeInIteration(obj, cx) && !cx->zone()->needsIncrementalBarrier()) { obj->reverseDenseElementsNoPreBarrier(length); return DenseElementResult::Success; } RootedValue origlo(cx), orighi(cx); uint32_t lo = 0, hi = length - 1; for (; lo < hi; lo++, hi--) { origlo = obj->getDenseElement(lo); orighi = obj->getDenseElement(hi); obj->setDenseElement(lo, orighi); if (orighi.isMagic(JS_ELEMENTS_HOLE) && !SuppressDeletedProperty(cx, obj, INT_TO_JSID(lo))) { return DenseElementResult::Failure; } obj->setDenseElement(hi, origlo); if (origlo.isMagic(JS_ELEMENTS_HOLE) && !SuppressDeletedProperty(cx, obj, INT_TO_JSID(hi))) { return DenseElementResult::Failure; } } return DenseElementResult::Success; } // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce // 22.1.3.21 Array.prototype.reverse ( ) bool js::array_reverse(JSContext* cx, unsigned argc, Value* vp) { AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.reverse"); CallArgs args = CallArgsFromVp(argc, vp); // Step 1. RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; // Step 2. uint64_t len; if (!GetLengthProperty(cx, obj, &len)) return false; // An empty array or an array with length 1 is already reversed. if (len <= 1) { args.rval().setObject(*obj); return true; } if (IsPackedArrayOrNoExtraIndexedProperties(obj, len) && len <= UINT32_MAX) { DenseElementResult result = ArrayReverseDenseKernel(cx, obj.as(), uint32_t(len)); if (result != DenseElementResult::Incomplete) { /* * Per ECMA-262, don't update the length of the array, even if the new * array has trailing holes (and thus the original array began with * holes). */ args.rval().setObject(*obj); return result == DenseElementResult::Success; } } // Steps 3-5. RootedValue lowval(cx), hival(cx); for (uint64_t i = 0, half = len / 2; i < half; i++) { bool hole, hole2; if (!CheckForInterrupt(cx) || !HasAndGetElement(cx, obj, i, &hole, &lowval) || !HasAndGetElement(cx, obj, len - i - 1, &hole2, &hival)) { return false; } if (!hole && !hole2) { if (!SetArrayElement(cx, obj, i, hival)) return false; if (!SetArrayElement(cx, obj, len - i - 1, lowval)) return false; } else if (hole && !hole2) { if (!SetArrayElement(cx, obj, i, hival)) return false; if (!DeletePropertyOrThrow(cx, obj, len - i - 1)) return false; } else if (!hole && hole2) { if (!DeletePropertyOrThrow(cx, obj, i)) return false; if (!SetArrayElement(cx, obj, len - i - 1, lowval)) return false; } else { // No action required. } } // Step 6. args.rval().setObject(*obj); return true; } static inline bool CompareStringValues(JSContext* cx, const Value& a, const Value& b, bool* lessOrEqualp) { if (!CheckForInterrupt(cx)) return false; JSString* astr = a.toString(); JSString* bstr = b.toString(); int32_t result; if (!CompareStrings(cx, astr, bstr, &result)) return false; *lessOrEqualp = (result <= 0); return true; } static const uint64_t powersOf10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 1000000000000ULL }; static inline unsigned NumDigitsBase10(uint32_t n) { /* * This is just floor_log10(n) + 1 * Algorithm taken from * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 */ uint32_t log2 = CeilingLog2(n); uint32_t t = log2 * 1233 >> 12; return t - (n < powersOf10[t]) + 1; } static inline bool CompareLexicographicInt32(const Value& a, const Value& b, bool* lessOrEqualp) { int32_t aint = a.toInt32(); int32_t bint = b.toInt32(); /* * If both numbers are equal ... trivial * If only one of both is negative --> arithmetic comparison as char code * of '-' is always less than any other digit * If both numbers are negative convert them to positive and continue * handling ... */ if (aint == bint) { *lessOrEqualp = true; } else if ((aint < 0) && (bint >= 0)) { *lessOrEqualp = true; } else if ((aint >= 0) && (bint < 0)) { *lessOrEqualp = false; } else { uint32_t auint = Abs(aint); uint32_t buint = Abs(bint); /* * ... get number of digits of both integers. * If they have the same number of digits --> arithmetic comparison. * If digits_a > digits_b: a < b*10e(digits_a - digits_b). * If digits_b > digits_a: a*10e(digits_b - digits_a) <= b. */ unsigned digitsa = NumDigitsBase10(auint); unsigned digitsb = NumDigitsBase10(buint); if (digitsa == digitsb) { *lessOrEqualp = (auint <= buint); } else if (digitsa > digitsb) { MOZ_ASSERT((digitsa - digitsb) < ArrayLength(powersOf10)); *lessOrEqualp = (uint64_t(auint) < uint64_t(buint) * powersOf10[digitsa - digitsb]); } else { /* if (digitsb > digitsa) */ MOZ_ASSERT((digitsb - digitsa) < ArrayLength(powersOf10)); *lessOrEqualp = (uint64_t(auint) * powersOf10[digitsb - digitsa] <= uint64_t(buint)); } } return true; } template static inline bool CompareSubStringValues(JSContext* cx, const Char1* s1, size_t len1, const Char2* s2, size_t len2, bool* lessOrEqualp) { if (!CheckForInterrupt(cx)) return false; if (!s1 || !s2) return false; int32_t result = CompareChars(s1, len1, s2, len2); *lessOrEqualp = (result <= 0); return true; } namespace { struct SortComparatorStrings { JSContext* const cx; explicit SortComparatorStrings(JSContext* cx) : cx(cx) {} bool operator()(const Value& a, const Value& b, bool* lessOrEqualp) { return CompareStringValues(cx, a, b, lessOrEqualp); } }; struct SortComparatorLexicographicInt32 { bool operator()(const Value& a, const Value& b, bool* lessOrEqualp) { return CompareLexicographicInt32(a, b, lessOrEqualp); } }; struct StringifiedElement { size_t charsBegin; size_t charsEnd; size_t elementIndex; }; struct SortComparatorStringifiedElements { JSContext* const cx; const StringBuffer& sb; SortComparatorStringifiedElements(JSContext* cx, const StringBuffer& sb) : cx(cx), sb(sb) {} bool operator()(const StringifiedElement& a, const StringifiedElement& b, bool* lessOrEqualp) { size_t lenA = a.charsEnd - a.charsBegin; size_t lenB = b.charsEnd - b.charsBegin; if (sb.isUnderlyingBufferLatin1()) { return CompareSubStringValues(cx, sb.rawLatin1Begin() + a.charsBegin, lenA, sb.rawLatin1Begin() + b.charsBegin, lenB, lessOrEqualp); } return CompareSubStringValues(cx, sb.rawTwoByteBegin() + a.charsBegin, lenA, sb.rawTwoByteBegin() + b.charsBegin, lenB, lessOrEqualp); } }; struct NumericElement { double dv; size_t elementIndex; }; static bool ComparatorNumericLeftMinusRight(const NumericElement& a, const NumericElement& b, bool* lessOrEqualp) { *lessOrEqualp = (a.dv <= b.dv); return true; } static bool ComparatorNumericRightMinusLeft(const NumericElement& a, const NumericElement& b, bool* lessOrEqualp) { *lessOrEqualp = (b.dv <= a.dv); return true; } typedef bool (*ComparatorNumeric)(const NumericElement& a, const NumericElement& b, bool* lessOrEqualp); static const ComparatorNumeric SortComparatorNumerics[] = { nullptr, nullptr, ComparatorNumericLeftMinusRight, ComparatorNumericRightMinusLeft }; static bool ComparatorInt32LeftMinusRight(const Value& a, const Value& b, bool* lessOrEqualp) { *lessOrEqualp = (a.toInt32() <= b.toInt32()); return true; } static bool ComparatorInt32RightMinusLeft(const Value& a, const Value& b, bool* lessOrEqualp) { *lessOrEqualp = (b.toInt32() <= a.toInt32()); return true; } typedef bool (*ComparatorInt32)(const Value& a, const Value& b, bool* lessOrEqualp); static const ComparatorInt32 SortComparatorInt32s[] = { nullptr, nullptr, ComparatorInt32LeftMinusRight, ComparatorInt32RightMinusLeft }; // Note: Values for this enum must match up with SortComparatorNumerics // and SortComparatorInt32s. enum ComparatorMatchResult { Match_Failure = 0, Match_None, Match_LeftMinusRight, Match_RightMinusLeft }; } // namespace /* * Specialize behavior for comparator functions with particular common bytecode * patterns: namely, |return x - y| and |return y - x|. */ static ComparatorMatchResult MatchNumericComparator(JSContext* cx, JSObject* obj) { if (!obj->is()) return Match_None; RootedFunction fun(cx, &obj->as()); if (!fun->isInterpreted() || fun->isClassConstructor()) return Match_None; JSScript* script = JSFunction::getOrCreateScript(cx, fun); if (!script) return Match_Failure; jsbytecode* pc = script->code(); uint16_t arg0, arg1; if (JSOp(*pc) != JSOP_GETARG) return Match_None; arg0 = GET_ARGNO(pc); pc += JSOP_GETARG_LENGTH; if (JSOp(*pc) != JSOP_GETARG) return Match_None; arg1 = GET_ARGNO(pc); pc += JSOP_GETARG_LENGTH; if (JSOp(*pc) != JSOP_SUB) return Match_None; pc += JSOP_SUB_LENGTH; if (JSOp(*pc) != JSOP_RETURN) return Match_None; if (arg0 == 0 && arg1 == 1) return Match_LeftMinusRight; if (arg0 == 1 && arg1 == 0) return Match_RightMinusLeft; return Match_None; } template static inline bool MergeSortByKey(K keys, size_t len, K scratch, C comparator, MutableHandle> vec) { MOZ_ASSERT(vec.length() >= len); /* Sort keys. */ if (!MergeSort(keys, len, scratch, comparator)) return false; /* * Reorder vec by keys in-place, going element by element. When an out-of- * place element is encountered, move that element to its proper position, * displacing whatever element was at *that* point to its proper position, * and so on until an element must be moved to the current position. * * At each outer iteration all elements up to |i| are sorted. If * necessary each inner iteration moves some number of unsorted elements * (including |i|) directly to sorted position. Thus on completion |*vec| * is sorted, and out-of-position elements have moved once. Complexity is * Θ(len) + O(len) == O(2*len), with each element visited at most twice. */ for (size_t i = 0; i < len; i++) { size_t j = keys[i].elementIndex; if (i == j) continue; // fixed point MOZ_ASSERT(j > i, "Everything less than |i| should be in the right place!"); Value tv = vec[j]; do { size_t k = keys[j].elementIndex; keys[j].elementIndex = j; vec[j].set(vec[k]); j = k; } while (j != i); // We could assert the loop invariant that |i == keys[i].elementIndex| // here if we synced |keys[i].elementIndex|. But doing so would render // the assertion vacuous, so don't bother, even in debug builds. vec[i].set(tv); } return true; } /* * Sort Values as strings. * * To minimize #conversions, SortLexicographically() first converts all Values * to strings at once, then sorts the elements by these cached strings. */ static bool SortLexicographically(JSContext* cx, MutableHandle> vec, size_t len) { MOZ_ASSERT(vec.length() >= len); StringBuffer sb(cx); Vector strElements(cx); /* MergeSort uses the upper half as scratch space. */ if (!strElements.resize(2 * len)) return false; /* Convert Values to strings. */ size_t cursor = 0; for (size_t i = 0; i < len; i++) { if (!CheckForInterrupt(cx)) return false; if (!ValueToStringBuffer(cx, vec[i], sb)) return false; strElements[i] = { cursor, sb.length(), i }; cursor = sb.length(); } /* Sort Values in vec alphabetically. */ return MergeSortByKey(strElements.begin(), len, strElements.begin() + len, SortComparatorStringifiedElements(cx, sb), vec); } /* * Sort Values as numbers. * * To minimize #conversions, SortNumerically first converts all Values to * numerics at once, then sorts the elements by these cached numerics. */ static bool SortNumerically(JSContext* cx, MutableHandle> vec, size_t len, ComparatorMatchResult comp) { MOZ_ASSERT(vec.length() >= len); Vector numElements(cx); /* MergeSort uses the upper half as scratch space. */ if (!numElements.resize(2 * len)) return false; /* Convert Values to numerics. */ for (size_t i = 0; i < len; i++) { if (!CheckForInterrupt(cx)) return false; double dv; if (!ToNumber(cx, vec[i], &dv)) return false; numElements[i] = { dv, i }; } /* Sort Values in vec numerically. */ return MergeSortByKey(numElements.begin(), len, numElements.begin() + len, SortComparatorNumerics[comp], vec); } static bool FillWithUndefined(JSContext* cx, HandleObject obj, uint32_t start, uint32_t count) { MOZ_ASSERT(start < start + count, "count > 0 and start + count doesn't overflow"); do { if (ObjectMayHaveExtraIndexedProperties(obj)) break; NativeObject* nobj = &obj->as(); if (!nobj->isExtensible()) break; if (obj->is() && !obj->as().lengthIsWritable() && start + count >= obj->as().length()) { break; } DenseElementResult result = nobj->ensureDenseElements(cx, start, count); if (result != DenseElementResult::Success) { if (result == DenseElementResult::Failure) return false; MOZ_ASSERT(result == DenseElementResult::Incomplete); break; } if (obj->is() && start + count >= obj->as().length()) obj->as().setLengthInt32(start + count); for (uint32_t i = 0; i < count; i++) nobj->setDenseElementWithType(cx, start + i, UndefinedHandleValue); return true; } while (false); for (uint32_t i = 0; i < count; i++) { if (!CheckForInterrupt(cx) || !SetArrayElement(cx, obj, start + i, UndefinedHandleValue)) return false; } return true; } bool js::intrinsic_ArrayNativeSort(JSContext* cx, unsigned argc, Value* vp) { // This function is called from the self-hosted Array.prototype.sort // implementation. It returns |true| if the array was sorted, otherwise it // returns |false| to notify the self-hosted code to perform the sorting. CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); HandleValue fval = args[0]; MOZ_ASSERT(fval.isUndefined() || IsCallable(fval)); ComparatorMatchResult comp; if (fval.isObject()) { comp = MatchNumericComparator(cx, &fval.toObject()); if (comp == Match_Failure) return false; if (comp == Match_None) { // Non-optimized user supplied comparators perform much better when // called from within a self-hosted sorting function. args.rval().setBoolean(false); return true; } } else { comp = Match_None; } RootedObject obj(cx, &args.thisv().toObject()); uint64_t length; if (!GetLengthProperty(cx, obj, &length)) return false; if (length < 2) { /* [] and [a] remain unchanged when sorted. */ args.rval().setBoolean(true); return true; } if (length > UINT32_MAX) { ReportAllocationOverflow(cx); return false; } uint32_t len = uint32_t(length); /* * We need a temporary array of 2 * len Value to hold the array elements * and the scratch space for merge sort. Check that its size does not * overflow size_t, which would allow for indexing beyond the end of the * malloc'd vector. */ #if JS_BITS_PER_WORD == 32 if (size_t(len) > size_t(-1) / (2 * sizeof(Value))) { ReportAllocationOverflow(cx); return false; } #endif size_t n, undefs; { Rooted> vec(cx, GCVector(cx)); if (!vec.reserve(2 * size_t(len))) return false; /* * By ECMA 262, 15.4.4.11, a property that does not exist (which we * call a "hole") is always greater than an existing property with * value undefined and that is always greater than any other property. * Thus to sort holes and undefs we simply count them, sort the rest * of elements, append undefs after them and then make holes after * undefs. */ undefs = 0; bool allStrings = true; bool allInts = true; bool extraIndexed; RootedValue v(cx); if (IsPackedArray(obj)) { HandleArrayObject array = obj.as(); extraIndexed = false; for (uint32_t i = 0; i < len; i++) { if (!CheckForInterrupt(cx)) return false; v.set(array->getDenseElement(i)); MOZ_ASSERT(!v.isMagic(JS_ELEMENTS_HOLE)); if (v.isUndefined()) { ++undefs; continue; } vec.infallibleAppend(v); allStrings = allStrings && v.isString(); allInts = allInts && v.isInt32(); } } else { extraIndexed = ObjectMayHaveExtraIndexedProperties(obj); for (uint32_t i = 0; i < len; i++) { if (!CheckForInterrupt(cx)) return false; bool hole; if (!HasAndGetElement(cx, obj, i, &hole, &v)) return false; if (hole) continue; if (v.isUndefined()) { ++undefs; continue; } vec.infallibleAppend(v); allStrings = allStrings && v.isString(); allInts = allInts && v.isInt32(); } } /* * If the array only contains holes, we're done. But if it contains * undefs, those must be sorted to the front of the array. */ n = vec.length(); if (n == 0 && undefs == 0) { args.rval().setBoolean(true); return true; } /* Here len == n + undefs + number_of_holes. */ if (comp == Match_None) { /* * Sort using the default comparator converting all elements to * strings. */ if (allStrings) { MOZ_ALWAYS_TRUE(vec.resize(n * 2)); if (!MergeSort(vec.begin(), n, vec.begin() + n, SortComparatorStrings(cx))) return false; } else if (allInts) { MOZ_ALWAYS_TRUE(vec.resize(n * 2)); if (!MergeSort(vec.begin(), n, vec.begin() + n, SortComparatorLexicographicInt32())) { return false; } } else { if (!SortLexicographically(cx, &vec, n)) return false; } } else { if (allInts) { MOZ_ALWAYS_TRUE(vec.resize(n * 2)); if (!MergeSort(vec.begin(), n, vec.begin() + n, SortComparatorInt32s[comp])) return false; } else { if (!SortNumerically(cx, &vec, n, comp)) return false; } } // We can omit the type update when neither collecting the elements // nor calling the default comparator can execute a (getter) function // that might run user code. ShouldUpdateTypes updateTypes = !extraIndexed && (allStrings || allInts) ? ShouldUpdateTypes::DontUpdate : ShouldUpdateTypes::Update; if (!SetArrayElements(cx, obj, 0, uint32_t(n), vec.begin(), updateTypes)) return false; } /* Set undefs that sorted after the rest of elements. */ if (undefs > 0) { if (!FillWithUndefined(cx, obj, n, undefs)) return false; n += undefs; } /* Re-create any holes that sorted to the end of the array. */ while (len > n) { if (!CheckForInterrupt(cx) || !DeletePropertyOrThrow(cx, obj, --len)) return false; } args.rval().setBoolean(true); return true; } bool js::NewbornArrayPush(JSContext* cx, HandleObject obj, const Value& v) { HandleArrayObject arr = obj.as(); MOZ_ASSERT(!v.isMagic()); MOZ_ASSERT(arr->lengthIsWritable()); uint32_t length = arr->length(); MOZ_ASSERT(length <= arr->getDenseCapacity()); if (!arr->ensureElements(cx, length + 1)) return false; arr->setDenseInitializedLength(length + 1); arr->setLengthInt32(length + 1); arr->initDenseElementWithType(cx, length, v); return true; } // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce // 22.1.3.18 Array.prototype.push ( ...items ) bool js::array_push(JSContext* cx, unsigned argc, Value* vp) { AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.push"); CallArgs args = CallArgsFromVp(argc, vp); // Step 1. RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; // Step 2. uint64_t length; if (!GetLengthProperty(cx, obj, &length)) return false; if (!ObjectMayHaveExtraIndexedProperties(obj) && length <= UINT32_MAX) { DenseElementResult result = obj->as().setOrExtendDenseElements(cx, uint32_t(length), args.array(), args.length()); if (result != DenseElementResult::Incomplete) { if (result == DenseElementResult::Failure) return false; uint32_t newlength = uint32_t(length) + args.length(); args.rval().setNumber(newlength); // setOrExtendDenseElements takes care of updating the length for // arrays. Handle updates to the length of non-arrays here. if (!obj->is()) { MOZ_ASSERT(obj->is()); return SetLengthProperty(cx, obj, newlength); } return true; } } // Step 5. uint64_t newlength = length + args.length(); if (newlength >= uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TOO_LONG_ARRAY); return false; } // Steps 3-6. if (!SetArrayElements(cx, obj, length, args.length(), args.array())) return false; // Steps 7-8. args.rval().setNumber(double(newlength)); return SetLengthProperty(cx, obj, newlength); } // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce // 22.1.3.17 Array.prototype.pop ( ) bool js::array_pop(JSContext* cx, unsigned argc, Value* vp) { AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.pop"); CallArgs args = CallArgsFromVp(argc, vp); // Step 1. RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; // Step 2. uint64_t index; if (!GetLengthProperty(cx, obj, &index)) return false; // Steps 3-4. if (index == 0) { // Step 3.b. args.rval().setUndefined(); } else { // Steps 4.a-b. index--; // Steps 4.c, 4.f. if (!GetArrayElement(cx, obj, index, args.rval())) return false; // Steps 4.d. if (!DeletePropertyOrThrow(cx, obj, index)) return false; } // Steps 3.a, 4.e. return SetLengthProperty(cx, obj, index); } void js::ArrayShiftMoveElements(NativeObject* obj) { AutoUnsafeCallWithABI unsafe; MOZ_ASSERT(obj->isExtensible()); MOZ_ASSERT_IF(obj->is(), obj->as().lengthIsWritable()); size_t initlen = obj->getDenseInitializedLength(); MOZ_ASSERT(initlen > 0); if (!obj->tryShiftDenseElements(1)) obj->moveDenseElementsNoPreBarrier(0, 1, initlen - 1); } static inline void SetInitializedLength(JSContext* cx, NativeObject* obj, size_t initlen) { MOZ_ASSERT(obj->isExtensible()); size_t oldInitlen = obj->getDenseInitializedLength(); obj->setDenseInitializedLength(initlen); if (initlen < oldInitlen) obj->shrinkElements(cx, initlen); } static DenseElementResult MoveDenseElements(JSContext* cx, NativeObject* obj, uint32_t dstStart, uint32_t srcStart, uint32_t length) { MOZ_ASSERT(obj->isExtensible()); if (!obj->maybeCopyElementsForWrite(cx)) return DenseElementResult::Failure; obj->moveDenseElements(dstStart, srcStart, length); return DenseElementResult::Success; } static DenseElementResult ArrayShiftDenseKernel(JSContext* cx, HandleObject obj, MutableHandleValue rval) { if (!IsPackedArray(obj) && ObjectMayHaveExtraIndexedProperties(obj)) return DenseElementResult::Incomplete; if (MaybeInIteration(obj, cx)) return DenseElementResult::Incomplete; if (!obj->as().isExtensible()) return DenseElementResult::Incomplete; size_t initlen = obj->as().getDenseInitializedLength(); if (initlen == 0) return DenseElementResult::Incomplete; rval.set(obj->as().getDenseElement(0)); if (rval.isMagic(JS_ELEMENTS_HOLE)) rval.setUndefined(); if (obj->as().tryShiftDenseElements(1)) return DenseElementResult::Success; DenseElementResult result = MoveDenseElements(cx, &obj->as(), 0, 1, initlen - 1); if (result != DenseElementResult::Success) return result; SetInitializedLength(cx, obj.as(), initlen - 1); return DenseElementResult::Success; } // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce // 22.1.3.22 Array.prototype.shift ( ) bool js::array_shift(JSContext* cx, unsigned argc, Value* vp) { AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.shift"); CallArgs args = CallArgsFromVp(argc, vp); // Step 1. RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; // Step 2. uint64_t len; if (!GetLengthProperty(cx, obj, &len)) return false; // Step 3. if (len == 0) { // Step 3.a. if (!SetLengthProperty(cx, obj, uint32_t(0))) return false; // Step 3.b. args.rval().setUndefined(); return true; } uint64_t newlen = len - 1; /* Fast paths. */ uint64_t startIndex; DenseElementResult result = ArrayShiftDenseKernel(cx, obj, args.rval()); if (result != DenseElementResult::Incomplete) { if (result == DenseElementResult::Failure) return false; if (len <= UINT32_MAX) return SetLengthProperty(cx, obj, newlen); startIndex = UINT32_MAX - 1; } else { // Steps 4, 9. if (!GetElement(cx, obj, 0, args.rval())) return false; startIndex = 0; } // Steps 5-6. RootedValue value(cx); for (uint64_t i = startIndex; i < newlen; i++) { if (!CheckForInterrupt(cx)) return false; bool hole; if (!HasAndGetElement(cx, obj, i + 1, &hole, &value)) return false; if (hole) { if (!DeletePropertyOrThrow(cx, obj, i)) return false; } else { if (!SetArrayElement(cx, obj, i, value)) return false; } } // Step 7. if (!DeletePropertyOrThrow(cx, obj, newlen)) return false; // Step 8. return SetLengthProperty(cx, obj, newlen); } // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce // 22.1.3.29 Array.prototype.unshift ( ...items ) bool js::array_unshift(JSContext* cx, unsigned argc, Value* vp) { AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.unshift"); CallArgs args = CallArgsFromVp(argc, vp); // Step 1. RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; // Step 2. uint64_t length; if (!GetLengthProperty(cx, obj, &length)) return false; // Steps 3-4. if (args.length() > 0) { bool optimized = false; do { if (length > UINT32_MAX) break; if (ObjectMayHaveExtraIndexedProperties(obj)) break; if (MaybeInIteration(obj, cx)) break; NativeObject* nobj = &obj->as(); if (!nobj->isExtensible()) break; if (nobj->is() && !nobj->as().lengthIsWritable()) break; if (!nobj->tryUnshiftDenseElements(args.length())) { DenseElementResult result = nobj->ensureDenseElements(cx, uint32_t(length), args.length()); if (result != DenseElementResult::Success) { if (result == DenseElementResult::Failure) return false; MOZ_ASSERT(result == DenseElementResult::Incomplete); break; } if (length > 0) nobj->moveDenseElements(args.length(), 0, uint32_t(length)); } for (uint32_t i = 0; i < args.length(); i++) nobj->setDenseElementWithType(cx, i, args[i]); optimized = true; } while (false); if (!optimized) { if (length > 0) { uint64_t last = length; uint64_t upperIndex = last + args.length(); // Step 4.a. if (upperIndex >= uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TOO_LONG_ARRAY); return false; } // Steps 4.b-c. RootedValue value(cx); do { --last; --upperIndex; if (!CheckForInterrupt(cx)) return false; bool hole; if (!HasAndGetElement(cx, obj, last, &hole, &value)) return false; if (hole) { if (!DeletePropertyOrThrow(cx, obj, upperIndex)) return false; } else { if (!SetArrayElement(cx, obj, upperIndex, value)) return false; } } while (last != 0); } // Steps 4.d-f. /* Copy from args to the bottom of the array. */ if (!SetArrayElements(cx, obj, 0, args.length(), args.array())) return false; } } // Step 5. uint64_t newlength = length + args.length(); if (!SetLengthProperty(cx, obj, newlength)) return false; // Step 6. /* Follow Perl by returning the new array length. */ args.rval().setNumber(double(newlength)); return true; } enum class ArrayAccess { Read, Write }; /* * Returns true if this is a dense array whose properties ending at |endIndex| * (exclusive) may be accessed (get, set, delete) directly through its * contiguous vector of elements without fear of getters, setters, etc. along * the prototype chain, or of enumerators requiring notification of * modifications. */ template static bool CanOptimizeForDenseStorage(HandleObject arr, uint64_t endIndex, JSContext* cx) { /* If the desired properties overflow dense storage, we can't optimize. */ if (endIndex > UINT32_MAX) return false; if (Access == ArrayAccess::Read) { /* * Dense storage read access is possible for any packed array as long * as we only access properties within the initialized length. In all * other cases we need to ensure there are no other indexed properties * on this object or on the prototype chain. Callers are required to * clamp the read length, so it doesn't exceed the initialized length. */ if (IsPackedArray(arr) && endIndex <= arr->as().getDenseInitializedLength()) return true; return !ObjectMayHaveExtraIndexedProperties(arr); } /* There's no optimizing possible if it's not an array. */ if (!arr->is()) return false; /* If the length is non-writable, always pick the slow path */ if (!arr->as().lengthIsWritable()) return false; /* Also pick the slow path if the object is non-extensible. */ if (!arr->as().isExtensible()) return false; /* Also pick the slow path if the object is being iterated over. */ if (MaybeInIteration(arr, cx)) return false; /* Or we attempt to write to indices outside the initialized length. */ if (endIndex > arr->as().getDenseInitializedLength()) return false; /* * Now watch out for getters and setters along the prototype chain or in * other indexed properties on the object. Packed arrays don't have any * other indexed properties by definition. */ return IsPackedArray(arr) || !ObjectMayHaveExtraIndexedProperties(arr); } static ArrayObject* CopyDenseArrayElements(JSContext* cx, HandleNativeObject obj, uint32_t begin, uint32_t count) { size_t initlen = obj->getDenseInitializedLength(); MOZ_ASSERT(initlen <= UINT32_MAX, "initialized length shouldn't exceed UINT32_MAX"); uint32_t newlength = 0; if (initlen > begin) newlength = Min(initlen - begin, count); ArrayObject* narr = NewFullyAllocatedArrayTryReuseGroup(cx, obj, newlength); if (!narr) return nullptr; MOZ_ASSERT(count >= narr->length()); narr->setLength(cx, count); if (newlength > 0) narr->initDenseElements(obj, begin, newlength); return narr; } static bool CopyArrayElements(JSContext* cx, HandleObject obj, uint64_t begin, uint64_t count, HandleArrayObject result) { MOZ_ASSERT(result->length() == count); uint64_t startIndex = 0; RootedValue value(cx); // Use dense storage for new indexed properties where possible. { uint32_t index = 0; uint32_t limit = Min(count, JSID_INT_MAX); for (; index < limit; index++) { bool hole; if (!CheckForInterrupt(cx) || !HasAndGetElement(cx, obj, begin + index, &hole, &value)) { return false; } if (!hole) { DenseElementResult edResult = result->ensureDenseElements(cx, index, 1); if (edResult != DenseElementResult::Success) { if (edResult == DenseElementResult::Failure) return false; MOZ_ASSERT(edResult == DenseElementResult::Incomplete); if (!DefineDataElement(cx, result, index, value)) return false; break; } result->setDenseElementWithType(cx, index, value); } } startIndex = index + 1; } // Copy any remaining elements. for (uint64_t i = startIndex; i < count; i++) { bool hole; if (!CheckForInterrupt(cx) || !HasAndGetElement(cx, obj, begin + i, &hole, &value)) { return false; } if (!hole && !DefineArrayElement(cx, result, i, value)) return false; } return true; } static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUsed) { AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.splice"); CallArgs args = CallArgsFromVp(argc, vp); /* Step 1. */ RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; /* Step 2. */ uint64_t len; if (!GetLengthProperty(cx, obj, &len)) return false; /* Step 3. */ double relativeStart; if (!ToInteger(cx, args.get(0), &relativeStart)) return false; /* Step 4. */ uint64_t actualStart; if (relativeStart < 0) actualStart = Max(len + relativeStart, 0.0); else actualStart = Min(relativeStart, double(len)); /* Step 5. */ uint64_t actualDeleteCount; if (args.length() == 0) { /* Step 5.b. */ actualDeleteCount = 0; } else if (args.length() == 1) { /* Step 6.b. */ actualDeleteCount = len - actualStart; } else { /* Steps 7.b. */ double deleteCountDouble; if (!ToInteger(cx, args[1], &deleteCountDouble)) return false; /* Step 7.c. */ actualDeleteCount = Min(Max(deleteCountDouble, 0.0), double(len - actualStart)); /* Step 8. */ uint32_t insertCount = args.length() - 2; if (len + insertCount - actualDeleteCount >= DOUBLE_INTEGRAL_PRECISION_LIMIT) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TOO_LONG_ARRAY); return false; } } MOZ_ASSERT(actualStart + actualDeleteCount <= len); RootedObject arr(cx); if (IsArraySpecies(cx, obj)) { if (actualDeleteCount > UINT32_MAX) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); return false; } uint32_t count = uint32_t(actualDeleteCount); if (CanOptimizeForDenseStorage(obj, actualStart + count, cx)) { MOZ_ASSERT(actualStart <= UINT32_MAX, "if actualStart + count <= UINT32_MAX, then actualStart <= UINT32_MAX"); if (returnValueIsUsed) { /* Steps 9-12. */ arr = CopyDenseArrayElements(cx, obj.as(), uint32_t(actualStart), count); if (!arr) return false; } } else { /* Step 9. */ arr = NewFullyAllocatedArrayTryReuseGroup(cx, obj, count); if (!arr) return false; /* Steps 10-11. */ if (!CopyArrayElements(cx, obj, actualStart, count, arr.as())) return false; /* Step 12 (implicit). */ } } else { /* Steps 9. */ if (!ArraySpeciesCreate(cx, obj, actualDeleteCount, &arr)) return false; /* Steps 10, 11, 11.d. */ RootedValue fromValue(cx); for (uint64_t k = 0; k < actualDeleteCount; k++) { /* Step 11.a (implicit). */ if (!CheckForInterrupt(cx)) return false; /* Steps 11.b, 11.c.i. */ bool hole; if (!HasAndGetElement(cx, obj, actualStart + k, &hole, &fromValue)) return false; /* Step 11.c. */ if (!hole) { /* Step 11.c.ii. */ if (!DefineArrayElement(cx, arr, k, fromValue)) return false; } } /* Step 12. */ if (!SetLengthProperty(cx, arr, actualDeleteCount)) return false; } /* Step 14. */ uint32_t itemCount = (args.length() >= 2) ? (args.length() - 2) : 0; uint64_t finalLength = len - actualDeleteCount + itemCount; if (itemCount < actualDeleteCount) { /* Step 15: the array is being shrunk. */ uint64_t sourceIndex = actualStart + actualDeleteCount; uint64_t targetIndex = actualStart + itemCount; if (CanOptimizeForDenseStorage(obj, len, cx)) { MOZ_ASSERT(sourceIndex <= len && targetIndex <= len && len <= UINT32_MAX, "sourceIndex and targetIndex are uint32 array indices"); MOZ_ASSERT(finalLength < len, "finalLength is strictly less than len"); MOZ_ASSERT(obj->isNative()); /* Steps 15.a-b. */ if (targetIndex != 0 || !obj->as().tryShiftDenseElements(sourceIndex)) { DenseElementResult result = MoveDenseElements(cx, &obj->as(), uint32_t(targetIndex), uint32_t(sourceIndex), uint32_t(len - sourceIndex)); MOZ_ASSERT(result != DenseElementResult::Incomplete); if (result == DenseElementResult::Failure) return false; } /* Steps 15.c-d. */ SetInitializedLength(cx, obj.as(), finalLength); } else { /* * This is all very slow if the length is very large. We don't yet * have the ability to iterate in sorted order, so we just do the * pessimistic thing and let CheckForInterrupt handle the * fallout. */ /* Steps 15.a-b. */ RootedValue fromValue(cx); for (uint64_t from = sourceIndex, to = targetIndex; from < len; from++, to++) { /* Steps 15.b.i-ii (implicit). */ if (!CheckForInterrupt(cx)) return false; /* Steps 15.b.iii, 15.b.iv.1. */ bool hole; if (!HasAndGetElement(cx, obj, from, &hole, &fromValue)) return false; /* Steps 15.b.iv. */ if (hole) { /* Steps 15.b.v.1. */ if (!DeletePropertyOrThrow(cx, obj, to)) return false; } else { /* Step 15.b.iv.2. */ if (!SetArrayElement(cx, obj, to, fromValue)) return false; } } /* Steps 15.c-d. */ if (!DeletePropertiesOrThrow(cx, obj, len, finalLength)) return false; } } else if (itemCount > actualDeleteCount) { MOZ_ASSERT(actualDeleteCount <= UINT32_MAX); uint32_t deleteCount = uint32_t(actualDeleteCount); /* Step 16. */ /* * Optimize only if the array is already dense and we can extend it to * its new length. It would be wrong to extend the elements here for a * number of reasons. * * First, this could cause us to fall into the fast-path below. This * would cause elements to be moved into places past the non-writable * length. And when the dense initialized length is updated, that'll * cause the |in| operator to think that those elements actually exist, * even though, properly, setting them must fail. * * Second, extending the elements here will trigger assertions inside * ensureDenseElements that the elements aren't being extended past the * length of a non-writable array. This is because extending elements * will extend capacity -- which might extend them past a non-writable * length, violating the |capacity <= length| invariant for such * arrays. And that would make the various JITted fast-path method * implementations of [].push, [].unshift, and so on wrong. * * If the array length is non-writable, this method *will* throw. For * simplicity, have the slow-path code do it. (Also note that the slow * path may validly *not* throw -- if all the elements being moved are * holes.) */ if (obj->is() && !ObjectMayHaveExtraIndexedProperties(obj) && len <= UINT32_MAX) { HandleArrayObject arr = obj.as(); if (arr->lengthIsWritable() && arr->isExtensible()) { DenseElementResult result = arr->ensureDenseElements(cx, uint32_t(len), itemCount - deleteCount); if (result == DenseElementResult::Failure) return false; } } if (CanOptimizeForDenseStorage(obj, finalLength, cx)) { MOZ_ASSERT((actualStart + actualDeleteCount) <= len && len <= UINT32_MAX, "start and deleteCount are uint32 array indices"); MOZ_ASSERT(actualStart + itemCount <= UINT32_MAX, "can't overflow because |len - actualDeleteCount + itemCount <= UINT32_MAX| " "and |actualStart <= len - actualDeleteCount| are both true"); uint32_t start = uint32_t(actualStart); uint32_t length = uint32_t(len); DenseElementResult result = MoveDenseElements(cx, &obj->as(), start + itemCount, start + deleteCount, length - (start + deleteCount)); MOZ_ASSERT(result != DenseElementResult::Incomplete); if (result == DenseElementResult::Failure) return false; /* Steps 16.a-b. */ SetInitializedLength(cx, obj.as(), finalLength); } else { RootedValue fromValue(cx); for (uint64_t k = len - actualDeleteCount; k > actualStart; k--) { if (!CheckForInterrupt(cx)) return false; /* Step 16.b.i. */ uint64_t from = k + actualDeleteCount - 1; /* Step 16.b.ii. */ uint64_t to = k + itemCount - 1; /* Steps 16.b.iii, 16.b.iv.1. */ bool hole; if (!HasAndGetElement(cx, obj, from, &hole, &fromValue)) return false; /* Steps 16.b.iv. */ if (hole) { /* Step 16.b.v.1. */ if (!DeletePropertyOrThrow(cx, obj, to)) return false; } else { /* Step 16.b.iv.2. */ if (!SetArrayElement(cx, obj, to, fromValue)) return false; } } } } /* Step 13 (reordered). */ Value* items = args.array() + 2; /* Steps 17-18. */ if (!SetArrayElements(cx, obj, actualStart, itemCount, items)) return false; /* Step 19. */ if (!SetLengthProperty(cx, obj, finalLength)) return false; /* Step 20. */ if (returnValueIsUsed) args.rval().setObject(*arr); return true; } /* ES 2016 draft Mar 25, 2016 22.1.3.26. */ bool js::array_splice(JSContext* cx, unsigned argc, Value* vp) { return array_splice_impl(cx, argc, vp, true); } static bool array_splice_noRetVal(JSContext* cx, unsigned argc, Value* vp) { return array_splice_impl(cx, argc, vp, false); } struct SortComparatorIndexes { bool operator()(uint32_t a, uint32_t b, bool* lessOrEqualp) { *lessOrEqualp = (a <= b); return true; } }; // Returns all indexed properties in the range [begin, end) found on |obj| or // its proto chain. This function does not handle proxies, objects with // resolve/lookupProperty hooks or indexed getters, as those can introduce // new properties. In those cases, *success is set to |false|. static bool GetIndexedPropertiesInRange(JSContext* cx, HandleObject obj, uint64_t begin, uint64_t end, Vector& indexes, bool* success) { *success = false; // TODO: Add IdIsIndex with support for large indices. if (end > UINT32_MAX) return true; MOZ_ASSERT(begin <= UINT32_MAX); // First, look for proxies or class hooks that can introduce extra // properties. JSObject* pobj = obj; do { if (!pobj->isNative() || pobj->getClass()->getResolve() || pobj->getOpsLookupProperty()) return true; } while ((pobj = pobj->staticPrototype())); // Collect indexed property names. pobj = obj; do { // Append dense elements. NativeObject* nativeObj = &pobj->as(); uint32_t initLen = nativeObj->getDenseInitializedLength(); for (uint32_t i = begin; i < initLen && i < end; i++) { if (nativeObj->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) continue; if (!indexes.append(i)) return false; } // Append typed array elements. if (nativeObj->is()) { uint32_t len = nativeObj->as().length(); for (uint32_t i = begin; i < len && i < end; i++) { if (!indexes.append(i)) return false; } } // Append sparse elements. if (nativeObj->isIndexed()) { Shape::Range r(nativeObj->lastProperty()); for (; !r.empty(); r.popFront()) { Shape& shape = r.front(); jsid id = shape.propid(); uint32_t i; if (!IdIsIndex(id, &i)) continue; if (!(begin <= i && i < end)) continue; // Watch out for getters, they can add new properties. if (!shape.hasDefaultGetter()) return true; if (!indexes.append(i)) return false; } } } while ((pobj = pobj->staticPrototype())); // Sort the indexes. Vector tmp(cx); size_t n = indexes.length(); if (!tmp.resize(n)) return false; if (!MergeSort(indexes.begin(), n, tmp.begin(), SortComparatorIndexes())) return false; // Remove duplicates. if (!indexes.empty()) { uint32_t last = 0; for (size_t i = 1, len = indexes.length(); i < len; i++) { uint32_t elem = indexes[i]; if (indexes[last] != elem) { last++; indexes[last] = elem; } } if (!indexes.resize(last + 1)) return false; } *success = true; return true; } static bool SliceSparse(JSContext* cx, HandleObject obj, uint64_t begin, uint64_t end, HandleArrayObject result) { MOZ_ASSERT(begin <= end); Vector indexes(cx); bool success; if (!GetIndexedPropertiesInRange(cx, obj, begin, end, indexes, &success)) return false; if (!success) return CopyArrayElements(cx, obj, begin, end - begin, result); MOZ_ASSERT(end <= UINT32_MAX, "indices larger than UINT32_MAX should be rejected by GetIndexedPropertiesInRange"); RootedValue value(cx); for (uint32_t index : indexes) { MOZ_ASSERT(begin <= index && index < end); bool hole; if (!HasAndGetElement(cx, obj, index, &hole, &value)) return false; if (!hole && !DefineDataElement(cx, result, index - uint32_t(begin), value)) return false; } return true; } static JSObject* SliceArguments(JSContext* cx, Handle argsobj, uint32_t begin, uint32_t count) { MOZ_ASSERT(!argsobj->hasOverriddenLength() && !argsobj->isAnyElementDeleted()); MOZ_ASSERT(begin + count <= argsobj->initialLength()); ArrayObject* result = NewDenseFullyAllocatedArray(cx, count); if (!result) return nullptr; result->setDenseInitializedLength(count); MOZ_ASSERT(result->group()->unknownPropertiesDontCheckGeneration(), "The default array group has unknown properties, so we can directly initialize the" "dense elements without needing to update the indexed type set."); for (uint32_t index = 0; index < count; index++) { const Value& v = argsobj->element(begin + index); result->initDenseElement(index, v); } return result; } template static inline ArrayLength NormalizeSliceTerm(T value, ArrayLength length) { if (value < 0) { value += length; if (value < 0) return 0; } else if (double(value) > double(length)) { return length; } return ArrayLength(value); } static bool ArraySliceOrdinary(JSContext* cx, HandleObject obj, uint64_t begin, uint64_t end, MutableHandleValue rval) { if (begin > end) begin = end; if ((end - begin) > UINT32_MAX) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); return false; } uint32_t count = uint32_t(end - begin); if (CanOptimizeForDenseStorage(obj, end, cx)) { MOZ_ASSERT(begin <= UINT32_MAX, "if end <= UINT32_MAX, then begin <= UINT32_MAX"); JSObject* narr = CopyDenseArrayElements(cx, obj.as(), uint32_t(begin), count); if (!narr) return false; rval.setObject(*narr); return true; } if (obj->is()) { Handle argsobj = obj.as(); if (!argsobj->hasOverriddenLength() && !argsobj->isAnyElementDeleted()) { MOZ_ASSERT(begin <= UINT32_MAX, "begin is limited by |argsobj|'s length"); JSObject* narr = SliceArguments(cx, argsobj, uint32_t(begin), count); if (!narr) return false; rval.setObject(*narr); return true; } } RootedArrayObject narr(cx, NewPartlyAllocatedArrayTryReuseGroup(cx, obj, count)); if (!narr) return false; if (end <= UINT32_MAX) { if (js::GetElementsOp op = obj->getOpsGetElements()) { ElementAdder adder(cx, narr, count, ElementAdder::CheckHasElemPreserveHoles); if (!op(cx, obj, uint32_t(begin), uint32_t(end), &adder)) return false; rval.setObject(*narr); return true; } } if (obj->isNative() && obj->as().isIndexed() && count > 1000) { if (!SliceSparse(cx, obj, begin, end, narr)) return false; } else { if (!CopyArrayElements(cx, obj, begin, count, narr)) return false; } rval.setObject(*narr); return true; } /* ES 2016 draft Mar 25, 2016 22.1.3.23. */ bool js::array_slice(JSContext* cx, unsigned argc, Value* vp) { AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.slice"); CallArgs args = CallArgsFromVp(argc, vp); /* Step 1. */ RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; /* Step 2. */ uint64_t length; if (!GetLengthProperty(cx, obj, &length)) return false; uint64_t k = 0; uint64_t final = length; if (args.length() > 0) { double d; /* Step 3. */ if (!ToInteger(cx, args[0], &d)) return false; /* Step 4. */ k = NormalizeSliceTerm(d, length); if (args.hasDefined(1)) { /* Step 5. */ if (!ToInteger(cx, args[1], &d)) return false; /* Step 6. */ final = NormalizeSliceTerm(d, length); } } if (IsArraySpecies(cx, obj)) { /* Steps 7-12: Optimized for ordinary array. */ return ArraySliceOrdinary(cx, obj, k, final, args.rval()); } /* Step 7. */ uint64_t count = final > k ? final - k : 0; /* Step 8. */ RootedObject arr(cx); if (!ArraySpeciesCreate(cx, obj, count, &arr)) return false; /* Step 9. */ uint64_t n = 0; /* Step 10. */ RootedValue kValue(cx); while (k < final) { if (!CheckForInterrupt(cx)) return false; /* Steps 10.a-b, and 10.c.i. */ bool kNotPresent; if (!HasAndGetElement(cx, obj, k, &kNotPresent, &kValue)) return false; /* Step 10.c. */ if (!kNotPresent) { /* Steps 10.c.ii. */ if (!DefineArrayElement(cx, arr, n, kValue)) return false; } /* Step 10.d. */ k++; /* Step 10.e. */ n++; } /* Step 11. */ if (!SetLengthProperty(cx, arr, n)) return false; /* Step 12. */ args.rval().setObject(*arr); return true; } static bool ArraySliceDenseKernel(JSContext* cx, ArrayObject* arr, int32_t beginArg, int32_t endArg, ArrayObject* result) { uint32_t length = arr->length(); uint32_t begin = NormalizeSliceTerm(beginArg, length); uint32_t end = NormalizeSliceTerm(endArg, length); if (begin > end) begin = end; uint32_t count = end - begin; size_t initlen = arr->getDenseInitializedLength(); if (initlen > begin) { uint32_t newlength = Min(initlen - begin, count); if (newlength > 0) { if (!result->ensureElements(cx, newlength)) return false; result->initDenseElements(arr, begin, newlength); } } MOZ_ASSERT(count >= result->length()); result->setLength(cx, count); return true; } JSObject* js::array_slice_dense(JSContext* cx, HandleObject obj, int32_t begin, int32_t end, HandleObject result) { if (result && IsArraySpecies(cx, obj)) { if (!ArraySliceDenseKernel(cx, &obj->as(), begin, end, &result->as())) { return nullptr; } return result; } // Slower path if the JIT wasn't able to allocate an object inline. JS::AutoValueArray<4> argv(cx); argv[0].setUndefined(); argv[1].setObject(*obj); argv[2].setInt32(begin); argv[3].setInt32(end); if (!array_slice(cx, 2, argv.begin())) return nullptr; return &argv[0].toObject(); } static bool array_isArray(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); bool isArray = false; if (args.get(0).isObject()) { RootedObject obj(cx, &args[0].toObject()); if (!IsArray(cx, obj, &isArray)) return false; } args.rval().setBoolean(isArray); return true; } static bool ArrayFromCallArgs(JSContext* cx, CallArgs& args, HandleObject proto = nullptr) { ArrayObject* obj = NewCopiedArrayForCallingAllocationSite(cx, args.array(), args.length(), proto); if (!obj) return false; args.rval().setObject(*obj); return true; } static bool array_of(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (IsArrayConstructor(args.thisv()) || !IsConstructor(args.thisv())) { // IsArrayConstructor(this) will usually be true in practice. This is // the most common path. return ArrayFromCallArgs(cx, args); } // Step 4. RootedObject obj(cx); { FixedConstructArgs<1> cargs(cx); cargs[0].setNumber(args.length()); if (!Construct(cx, args.thisv(), cargs, args.thisv(), &obj)) return false; } // Step 8. for (unsigned k = 0; k < args.length(); k++) { if (!DefineDataElement(cx, obj, k, args[k])) return false; } // Steps 9-10. if (!SetLengthProperty(cx, obj, args.length())) return false; // Step 11. args.rval().setObject(*obj); return true; } const JSJitInfo js::array_splice_info = { { (JSJitGetterOp)array_splice_noRetVal }, { 0 }, /* unused */ { 0 }, /* unused */ JSJitInfo::IgnoresReturnValueNative, JSJitInfo::AliasEverything, JSVAL_TYPE_UNDEFINED, }; static const JSFunctionSpec array_methods[] = { JS_FN(js_toSource_str, array_toSource, 0,0), JS_SELF_HOSTED_FN(js_toString_str, "ArrayToString", 0,0), JS_FN(js_toLocaleString_str, array_toLocaleString, 0,0), /* Perl-ish methods. */ JS_INLINABLE_FN("join", array_join, 1,0, ArrayJoin), JS_FN("reverse", array_reverse, 0,0), JS_SELF_HOSTED_FN("sort", "ArraySort", 1,0), JS_INLINABLE_FN("push", array_push, 1,0, ArrayPush), JS_INLINABLE_FN("pop", array_pop, 0,0, ArrayPop), JS_INLINABLE_FN("shift", array_shift, 0,0, ArrayShift), JS_FN("unshift", array_unshift, 1,0), JS_FNINFO("splice", array_splice, &array_splice_info, 2,0), /* Pythonic sequence methods. */ JS_SELF_HOSTED_FN("concat", "ArrayConcat", 1,0), JS_INLINABLE_FN("slice", array_slice, 2,0, ArraySlice), JS_SELF_HOSTED_FN("lastIndexOf", "ArrayLastIndexOf", 1,0), JS_SELF_HOSTED_FN("indexOf", "ArrayIndexOf", 1,0), JS_SELF_HOSTED_FN("forEach", "ArrayForEach", 1,0), JS_SELF_HOSTED_FN("map", "ArrayMap", 1,0), JS_SELF_HOSTED_FN("filter", "ArrayFilter", 1,0), JS_SELF_HOSTED_FN("reduce", "ArrayReduce", 1,0), JS_SELF_HOSTED_FN("reduceRight", "ArrayReduceRight", 1,0), JS_SELF_HOSTED_FN("some", "ArraySome", 1,0), JS_SELF_HOSTED_FN("every", "ArrayEvery", 1,0), /* ES6 additions */ JS_SELF_HOSTED_FN("find", "ArrayFind", 1,0), JS_SELF_HOSTED_FN("findIndex", "ArrayFindIndex", 1,0), JS_SELF_HOSTED_FN("copyWithin", "ArrayCopyWithin", 3,0), JS_SELF_HOSTED_FN("fill", "ArrayFill", 3,0), JS_SELF_HOSTED_SYM_FN(iterator, "ArrayValues", 0,0), JS_SELF_HOSTED_FN("entries", "ArrayEntries", 0,0), JS_SELF_HOSTED_FN("keys", "ArrayKeys", 0,0), JS_SELF_HOSTED_FN("values", "ArrayValues", 0,0), /* ES7 additions */ JS_SELF_HOSTED_FN("includes", "ArrayIncludes", 2,0), /* Future additions */ JS_SELF_HOSTED_FN("flatMap", "ArrayFlatMap", 1,0), JS_SELF_HOSTED_FN("flat", "ArrayFlat", 0,0), JS_FS_END }; static const JSFunctionSpec array_static_methods[] = { JS_INLINABLE_FN("isArray", array_isArray, 1,0, ArrayIsArray), JS_SELF_HOSTED_FN("concat", "ArrayStaticConcat", 2,0), JS_SELF_HOSTED_FN("lastIndexOf", "ArrayStaticLastIndexOf", 2,0), JS_SELF_HOSTED_FN("indexOf", "ArrayStaticIndexOf", 2,0), JS_SELF_HOSTED_FN("forEach", "ArrayStaticForEach", 2,0), JS_SELF_HOSTED_FN("map", "ArrayStaticMap", 2,0), JS_SELF_HOSTED_FN("filter", "ArrayStaticFilter", 2,0), JS_SELF_HOSTED_FN("every", "ArrayStaticEvery", 2,0), JS_SELF_HOSTED_FN("some", "ArrayStaticSome", 2,0), JS_SELF_HOSTED_FN("reduce", "ArrayStaticReduce", 2,0), JS_SELF_HOSTED_FN("reduceRight", "ArrayStaticReduceRight", 2,0), JS_SELF_HOSTED_FN("join", "ArrayStaticJoin", 2,0), JS_SELF_HOSTED_FN("reverse", "ArrayStaticReverse", 1,0), JS_SELF_HOSTED_FN("sort", "ArrayStaticSort", 2,0), JS_SELF_HOSTED_FN("push", "ArrayStaticPush", 2,0), JS_SELF_HOSTED_FN("pop", "ArrayStaticPop", 1,0), JS_SELF_HOSTED_FN("shift", "ArrayStaticShift", 1,0), JS_SELF_HOSTED_FN("unshift", "ArrayStaticUnshift", 2,0), JS_SELF_HOSTED_FN("splice", "ArrayStaticSplice", 3,0), JS_SELF_HOSTED_FN("slice", "ArrayStaticSlice", 3,0), JS_SELF_HOSTED_FN("from", "ArrayFrom", 3,0), JS_FN("of", array_of, 0,0), JS_FS_END }; const JSPropertySpec array_static_props[] = { JS_SELF_HOSTED_SYM_GET(species, "ArraySpecies", 0), JS_PS_END }; static inline bool ArrayConstructorImpl(JSContext* cx, CallArgs& args, bool isConstructor) { RootedObject proto(cx); if (isConstructor) { if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) return false; } else { // We're emulating |new Array(n)| with |std_Array(n)| in self-hosted JS, // and the proto should be %ArrayPrototype% regardless of the callee. proto = GlobalObject::getOrCreateArrayPrototype(cx, cx->global()); if (!proto) return false; } if (args.length() != 1 || !args[0].isNumber()) return ArrayFromCallArgs(cx, args, proto); uint32_t length; if (args[0].isInt32()) { int32_t i = args[0].toInt32(); if (i < 0) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); return false; } length = uint32_t(i); } else { double d = args[0].toDouble(); length = ToUint32(d); if (d != double(length)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); return false; } } ArrayObject* obj = NewPartlyAllocatedArrayForCallingAllocationSite(cx, length, proto); if (!obj) return false; args.rval().setObject(*obj); return true; } /* ES5 15.4.2 */ bool js::ArrayConstructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return ArrayConstructorImpl(cx, args, /* isConstructor = */ true); } bool js::array_construct(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(!args.isConstructing()); MOZ_ASSERT(args.length() == 1); MOZ_ASSERT(args[0].isNumber()); return ArrayConstructorImpl(cx, args, /* isConstructor = */ false); } ArrayObject* js::ArrayConstructorOneArg(JSContext* cx, HandleObjectGroup group, int32_t lengthInt) { if (lengthInt < 0) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); return nullptr; } uint32_t length = uint32_t(lengthInt); return NewPartlyAllocatedArrayTryUseGroup(cx, group, length); } static JSObject* CreateArrayPrototype(JSContext* cx, JSProtoKey key) { MOZ_ASSERT(key == JSProto_Array); RootedObject proto(cx, GlobalObject::getOrCreateObjectPrototype(cx, cx->global())); if (!proto) return nullptr; RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &ArrayObject::class_, TaggedProto(proto))); if (!group) return nullptr; RootedShape shape(cx, EmptyShape::getInitialShape(cx, &ArrayObject::class_, TaggedProto(proto), gc::AllocKind::OBJECT0)); if (!shape) return nullptr; AutoSetNewObjectMetadata metadata(cx); RootedArrayObject arrayProto(cx, ArrayObject::createArray(cx, gc::AllocKind::OBJECT4, gc::TenuredHeap, shape, group, 0, metadata)); if (!arrayProto || !JSObject::setSingleton(cx, arrayProto) || !JSObject::setDelegate(cx, arrayProto) || !AddLengthProperty(cx, arrayProto)) { return nullptr; } /* * The default 'new' group of Array.prototype is required by type inference * to have unknown properties, to simplify handling of e.g. heterogenous * arrays in JSON and script literals and allows setDenseArrayElement to * be used without updating the indexed type set for such default arrays. */ ObjectGroupRealm& realm = ObjectGroupRealm::getForNewObject(cx); if (!JSObject::setNewGroupUnknown(cx, realm, &ArrayObject::class_, arrayProto)) return nullptr; return arrayProto; } static bool array_proto_finish(JSContext* cx, JS::HandleObject ctor, JS::HandleObject proto) { // Add Array.prototype[@@unscopables]. ECMA-262 draft (2016 Mar 19) 22.1.3.32. RootedObject unscopables(cx, NewObjectWithGivenProto(cx, nullptr, SingletonObject)); if (!unscopables) return false; RootedValue value(cx, BooleanValue(true)); if (!DefineDataProperty(cx, unscopables, cx->names().copyWithin, value) || !DefineDataProperty(cx, unscopables, cx->names().entries, value) || !DefineDataProperty(cx, unscopables, cx->names().fill, value) || !DefineDataProperty(cx, unscopables, cx->names().find, value) || !DefineDataProperty(cx, unscopables, cx->names().findIndex, value) || !DefineDataProperty(cx, unscopables, cx->names().includes, value) || !DefineDataProperty(cx, unscopables, cx->names().keys, value) || !DefineDataProperty(cx, unscopables, cx->names().values, value)) { return false; } RootedId id(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().get(JS::SymbolCode::unscopables))); value.setObject(*unscopables); return DefineDataProperty(cx, proto, id, value, JSPROP_READONLY); } static const ClassOps ArrayObjectClassOps = { array_addProperty, nullptr, /* delProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ nullptr, /* trace */ }; static const ClassSpec ArrayObjectClassSpec = { GenericCreateConstructor, CreateArrayPrototype, array_static_methods, array_static_props, array_methods, nullptr, array_proto_finish }; const Class ArrayObject::class_ = { "Array", JSCLASS_HAS_CACHED_PROTO(JSProto_Array) | JSCLASS_DELAY_METADATA_BUILDER, &ArrayObjectClassOps, &ArrayObjectClassSpec }; /* * Array allocation functions. */ static inline bool EnsureNewArrayElements(JSContext* cx, ArrayObject* obj, uint32_t length) { /* * If ensureElements creates dynamically allocated slots, then having * fixedSlots is a waste. */ DebugOnly cap = obj->getDenseCapacity(); if (!obj->ensureElements(cx, length)) return false; MOZ_ASSERT_IF(cap, !obj->hasDynamicElements()); return true; } template static MOZ_ALWAYS_INLINE ArrayObject* NewArray(JSContext* cx, uint32_t length, HandleObject protoArg, NewObjectKind newKind = GenericObject) { gc::AllocKind allocKind = GuessArrayGCKind(length); MOZ_ASSERT(CanBeFinalizedInBackground(allocKind, &ArrayObject::class_)); allocKind = GetBackgroundAllocKind(allocKind); RootedObject proto(cx, protoArg); if (!proto) { proto = GlobalObject::getOrCreateArrayPrototype(cx, cx->global()); if (!proto) return nullptr; } Rooted taggedProto(cx, TaggedProto(proto)); bool isCachable = NewObjectWithTaggedProtoIsCachable(cx, taggedProto, newKind, &ArrayObject::class_); if (isCachable) { NewObjectCache& cache = cx->caches().newObjectCache; NewObjectCache::EntryIndex entry = -1; if (cache.lookupProto(&ArrayObject::class_, proto, allocKind, &entry)) { gc::InitialHeap heap = GetInitialHeap(newKind, &ArrayObject::class_); AutoSetNewObjectMetadata metadata(cx); JSObject* obj = cache.newObjectFromHit(cx, entry, heap); if (obj) { /* Fixup the elements pointer and length, which may be incorrect. */ ArrayObject* arr = &obj->as(); arr->setFixedElements(); arr->setLength(cx, length); if (maxLength > 0 && !EnsureNewArrayElements(cx, arr, std::min(maxLength, length))) { return nullptr; } return arr; } } } RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &ArrayObject::class_, TaggedProto(proto))); if (!group) return nullptr; /* * Get a shape with zero fixed slots, regardless of the size class. * See JSObject::createArray. */ RootedShape shape(cx, EmptyShape::getInitialShape(cx, &ArrayObject::class_, TaggedProto(proto), gc::AllocKind::OBJECT0)); if (!shape) return nullptr; AutoSetNewObjectMetadata metadata(cx); RootedArrayObject arr(cx, ArrayObject::createArray(cx, allocKind, GetInitialHeap(newKind, &ArrayObject::class_), shape, group, length, metadata)); if (!arr) return nullptr; if (shape->isEmptyShape()) { if (!AddLengthProperty(cx, arr)) return nullptr; shape = arr->lastProperty(); EmptyShape::insertInitialShape(cx, shape, proto); } if (newKind == SingletonObject && !JSObject::setSingleton(cx, arr)) return nullptr; if (isCachable) { NewObjectCache& cache = cx->caches().newObjectCache; NewObjectCache::EntryIndex entry = -1; cache.lookupProto(&ArrayObject::class_, proto, allocKind, &entry); cache.fillProto(entry, &ArrayObject::class_, taggedProto, allocKind, arr); } if (maxLength > 0 && !EnsureNewArrayElements(cx, arr, std::min(maxLength, length))) return nullptr; probes::CreateObject(cx, arr); return arr; } ArrayObject * JS_FASTCALL js::NewDenseEmptyArray(JSContext* cx, HandleObject proto /* = nullptr */, NewObjectKind newKind /* = GenericObject */) { return NewArray<0>(cx, 0, proto, newKind); } ArrayObject * JS_FASTCALL js::NewDenseFullyAllocatedArray(JSContext* cx, uint32_t length, HandleObject proto /* = nullptr */, NewObjectKind newKind /* = GenericObject */) { return NewArray(cx, length, proto, newKind); } ArrayObject * JS_FASTCALL js::NewDensePartlyAllocatedArray(JSContext* cx, uint32_t length, HandleObject proto /* = nullptr */, NewObjectKind newKind /* = GenericObject */) { return NewArray(cx, length, proto, newKind); } ArrayObject * JS_FASTCALL js::NewDenseUnallocatedArray(JSContext* cx, uint32_t length, HandleObject proto /* = nullptr */, NewObjectKind newKind /* = GenericObject */) { return NewArray<0>(cx, length, proto, newKind); } // values must point at already-rooted Value objects ArrayObject* js::NewDenseCopiedArray(JSContext* cx, uint32_t length, const Value* values, HandleObject proto /* = nullptr */, NewObjectKind newKind /* = GenericObject */) { ArrayObject* arr = NewArray(cx, length, proto, newKind); if (!arr) return nullptr; MOZ_ASSERT(arr->getDenseCapacity() >= length); MOZ_ASSERT(arr->getDenseInitializedLength() == 0); if (values) arr->initDenseElements(values, length); return arr; } ArrayObject* js::NewDenseFullyAllocatedArrayWithTemplate(JSContext* cx, uint32_t length, JSObject* templateObject) { AutoSetNewObjectMetadata metadata(cx); gc::AllocKind allocKind = GuessArrayGCKind(length); MOZ_ASSERT(CanBeFinalizedInBackground(allocKind, &ArrayObject::class_)); allocKind = GetBackgroundAllocKind(allocKind); RootedObjectGroup group(cx, templateObject->group()); RootedShape shape(cx, templateObject->as().lastProperty()); gc::InitialHeap heap = GetInitialHeap(GenericObject, &ArrayObject::class_); Rooted arr(cx, ArrayObject::createArray(cx, allocKind, heap, shape, group, length, metadata)); if (!arr) return nullptr; if (!EnsureNewArrayElements(cx, arr, length)) return nullptr; probes::CreateObject(cx, arr); return arr; } ArrayObject* js::NewDenseCopyOnWriteArray(JSContext* cx, HandleArrayObject templateObject, gc::InitialHeap heap) { MOZ_ASSERT(!gc::IsInsideNursery(templateObject)); ArrayObject* arr = ArrayObject::createCopyOnWriteArray(cx, heap, templateObject); if (!arr) return nullptr; probes::CreateObject(cx, arr); return arr; } // Return a new array with the specified length and allocated capacity (up to // maxLength), using the specified group if possible. If the specified group // cannot be used, ensure that the created array at least has the given // [[Prototype]]. template static inline ArrayObject* NewArrayTryUseGroup(JSContext* cx, HandleObjectGroup group, size_t length, NewObjectKind newKind = GenericObject) { MOZ_ASSERT(newKind != SingletonObject); { AutoSweepObjectGroup sweep(group); if (group->shouldPreTenure(sweep)) newKind = TenuredObject; } RootedObject proto(cx, group->proto().toObject()); ArrayObject* res = NewArray(cx, length, proto, newKind); if (!res) return nullptr; res->setGroup(group); // If the length calculation overflowed, make sure that is marked for the // new group. if (res->length() > INT32_MAX) res->setLength(cx, res->length()); return res; } ArrayObject* js::NewFullyAllocatedArrayTryUseGroup(JSContext* cx, HandleObjectGroup group, size_t length, NewObjectKind newKind) { return NewArrayTryUseGroup(cx, group, length, newKind); } ArrayObject* js::NewPartlyAllocatedArrayTryUseGroup(JSContext* cx, HandleObjectGroup group, size_t length) { return NewArrayTryUseGroup(cx, group, length); } // Return a new array with the default prototype and specified allocated // capacity and length. If possible, try to reuse the group of the input // object. The resulting array will either reuse the input object's group or // will have unknown property types. template static inline ArrayObject* NewArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length, NewObjectKind newKind = GenericObject) { if (!obj->is()) return NewArray(cx, length, nullptr, newKind); if (obj->staticPrototype() != cx->global()->maybeGetArrayPrototype()) return NewArray(cx, length, nullptr, newKind); RootedObjectGroup group(cx, JSObject::getGroup(cx, obj)); if (!group) return nullptr; return NewArrayTryUseGroup(cx, group, length, newKind); } ArrayObject* js::NewFullyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length, NewObjectKind newKind) { return NewArrayTryReuseGroup(cx, obj, length, newKind); } ArrayObject* js::NewPartlyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length) { return NewArrayTryReuseGroup(cx, obj, length); } ArrayObject* js::NewFullyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, NewObjectKind newKind) { RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array)); if (!group) return nullptr; return NewArrayTryUseGroup(cx, group, length, newKind); } ArrayObject* js::NewPartlyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, HandleObject proto) { RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array, proto)); if (!group) return nullptr; return NewArrayTryUseGroup(cx, group, length); } ArrayObject* js::NewCopiedArrayTryUseGroup(JSContext* cx, HandleObjectGroup group, const Value* vp, size_t length, NewObjectKind newKind, ShouldUpdateTypes updateTypes) { ArrayObject* obj = NewFullyAllocatedArrayTryUseGroup(cx, group, length, newKind); if (!obj) return nullptr; DenseElementResult result = obj->setOrExtendDenseElements(cx, 0, vp, length, updateTypes); if (result == DenseElementResult::Failure) return nullptr; MOZ_ASSERT(result == DenseElementResult::Success); return obj; } ArrayObject* js::NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length, HandleObject proto /* = nullptr */) { RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array, proto)); if (!group) return nullptr; return NewCopiedArrayTryUseGroup(cx, group, vp, length); } #ifdef DEBUG bool js::ArrayInfo(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx); for (unsigned i = 0; i < args.length(); i++) { HandleValue arg = args[i]; UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, arg, nullptr); if (!bytes) return false; if (arg.isPrimitive() || !(obj = arg.toObjectOrNull())->is()) { fprintf(stderr, "%s: not array\n", bytes.get()); continue; } fprintf(stderr, "%s: (len %u", bytes.get(), obj->as().length()); fprintf(stderr, ", capacity %u", obj->as().getDenseCapacity()); fputs(")\n", stderr); } args.rval().setUndefined(); return true; } #endif void js::ArraySpeciesLookup::initialize(JSContext* cx) { MOZ_ASSERT(state_ == State::Uninitialized); // Get the canonical Array.prototype. NativeObject* arrayProto = cx->global()->maybeGetArrayPrototype(); // Leave the cache uninitialized if the Array class itself is not yet // initialized. if (!arrayProto) return; // Get the canonical Array constructor. const Value& arrayCtorValue = cx->global()->getConstructor(JSProto_Array); MOZ_ASSERT(arrayCtorValue.isObject(), "The Array constructor is initialized iff Array.prototype is initialized"); JSFunction* arrayCtor = &arrayCtorValue.toObject().as(); // Shortcut returns below means Array[@@species] will never be // optimizable, set to disabled now, and clear it later when we succeed. state_ = State::Disabled; // Look up Array.prototype[@@iterator] and ensure it's a data property. Shape* ctorShape = arrayProto->lookup(cx, NameToId(cx->names().constructor)); if (!ctorShape || !ctorShape->isDataProperty()) return; // Get the referred value, and ensure it holds the canonical Array // constructor. JSFunction* ctorFun; if (!IsFunctionObject(arrayProto->getSlot(ctorShape->slot()), &ctorFun)) return; if (ctorFun != arrayCtor) return; // Look up the '@@species' value on Array Shape* speciesShape = arrayCtor->lookup(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().species)); if (!speciesShape || !speciesShape->hasGetterValue()) return; // Get the referred value, ensure it holds the canonical Array[@@species] // function. JSFunction* speciesFun; if (!IsFunctionObject(speciesShape->getterValue(), &speciesFun)) return; if (!IsSelfHostedFunctionWithName(speciesFun, cx->names().ArraySpecies)) return; // Store raw pointers below. This is okay to do here, because all objects // are in the tenured heap. MOZ_ASSERT(!IsInsideNursery(arrayProto)); MOZ_ASSERT(!IsInsideNursery(arrayCtor)); MOZ_ASSERT(!IsInsideNursery(arrayCtor->lastProperty())); MOZ_ASSERT(!IsInsideNursery(speciesShape)); MOZ_ASSERT(!IsInsideNursery(speciesFun)); MOZ_ASSERT(!IsInsideNursery(arrayProto->lastProperty())); state_ = State::Initialized; arrayProto_ = arrayProto; arrayConstructor_ = arrayCtor; arrayConstructorShape_ = arrayCtor->lastProperty(); #ifdef DEBUG arraySpeciesShape_ = speciesShape; canonicalSpeciesFunc_ = speciesFun; #endif arrayProtoShape_ = arrayProto->lastProperty(); arrayProtoConstructorSlot_ = ctorShape->slot(); } void js::ArraySpeciesLookup::reset() { - JS_POISON(this, 0xBB, sizeof(this), MemCheckKind::MakeUndefined); + JS_POISON(this, 0xBB, sizeof(*this), MemCheckKind::MakeUndefined); state_ = State::Uninitialized; } bool js::ArraySpeciesLookup::isArrayStateStillSane() { MOZ_ASSERT(state_ == State::Initialized); // Ensure that Array.prototype still has the expected shape. if (arrayProto_->lastProperty() != arrayProtoShape_) return false; // Ensure that Array.prototype.constructor contains the canonical Array // constructor function. if (arrayProto_->getSlot(arrayProtoConstructorSlot_) != ObjectValue(*arrayConstructor_)) return false; // Ensure that Array still has the expected shape. if (arrayConstructor_->lastProperty() != arrayConstructorShape_) 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. MOZ_ASSERT(arraySpeciesShape_->getterObject() == canonicalSpeciesFunc_); return true; } bool js::ArraySpeciesLookup::tryOptimizeArray(JSContext* cx, ArrayObject* array) { if (state_ == State::Uninitialized) { // If the cache is not initialized, initialize it. initialize(cx); } else if (state_ == State::Initialized && !isArrayStateStillSane()) { // Otherwise, if the array state is no longer sane, reinitialize. reset(); initialize(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 array state. MOZ_ASSERT(isArrayStateStillSane()); // Ensure |array|'s prototype is the actual Array.prototype. if (array->staticPrototype() != arrayProto_) return false; // Ensure |array| doesn't define any own properties besides its // non-deletable "length" property. This serves as a quick check to make // sure |array| doesn't define an own "constructor" property which may // shadow Array.prototype.constructor. Shape* shape = array->shape(); if (shape->previous() && !shape->previous()->isEmptyShape()) return false; MOZ_ASSERT(JSID_IS_ATOM(shape->propidRaw(), cx->names().length)); return true; } diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -1,4844 +1,4844 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 "builtin/Promise.h" #include "mozilla/Atomics.h" #include "mozilla/Maybe.h" #include "mozilla/TimeStamp.h" #include "jsexn.h" #include "jsfriendapi.h" #include "gc/Heap.h" #include "js/Debug.h" #include "vm/AsyncFunction.h" #include "vm/AsyncIteration.h" #include "vm/Debugger.h" #include "vm/Iteration.h" #include "vm/JSContext.h" #include "vm/JSObject.h" #include "vm/SelfHosting.h" #include "vm/Compartment-inl.h" #include "vm/Debugger-inl.h" #include "vm/JSObject-inl.h" #include "vm/NativeObject-inl.h" using namespace js; static double MillisecondsSinceStartup(const mozilla::Maybe& maybeNow) { auto now = maybeNow.isSome() ? maybeNow.ref() : mozilla::TimeStamp::Now(); return (now - mozilla::TimeStamp::ProcessCreation()).ToMilliseconds(); } enum PromiseHandler { PromiseHandlerIdentity = 0, PromiseHandlerThrower, // ES 2018 draft 25.5.5.4-5. PromiseHandlerAsyncFunctionAwaitedFulfilled, PromiseHandlerAsyncFunctionAwaitedRejected, // Async Iteration proposal 4.1. PromiseHandlerAsyncGeneratorAwaitedFulfilled, PromiseHandlerAsyncGeneratorAwaitedRejected, // Async Iteration proposal 11.4.3.5.1-2. PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled, PromiseHandlerAsyncGeneratorResumeNextReturnRejected, // Async Iteration proposal 11.4.3.7 steps 8.c-e. PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled, PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected, // Async Iteration proposal 11.1.3.2.5. // Async-from-Sync iterator handlers take the resolved value and create new // iterator objects. To do so it needs to forward whether the iterator is // done. In spec, this is achieved via the [[Done]] internal slot. We // enumerate both true and false cases here. PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone, PromiseHandlerAsyncFromSyncIteratorValueUnwrapNotDone, // One past the maximum allowed PromiseHandler value. PromiseHandlerLimit }; enum ResolutionMode { ResolveMode, RejectMode }; enum ResolveFunctionSlots { ResolveFunctionSlot_Promise = 0, ResolveFunctionSlot_RejectFunction, }; enum RejectFunctionSlots { RejectFunctionSlot_Promise = 0, RejectFunctionSlot_ResolveFunction, }; enum PromiseAllResolveElementFunctionSlots { PromiseAllResolveElementFunctionSlot_Data = 0, PromiseAllResolveElementFunctionSlot_ElementIndex, }; enum ReactionJobSlots { ReactionJobSlot_ReactionRecord = 0, }; enum ThenableJobSlots { // The handler to use as the Promise reaction. It is a callable object // that's guaranteed to be from the same compartment as the // PromiseReactionJob. ThenableJobSlot_Handler = 0, // JobData - a, potentially CCW-wrapped, dense list containing data // required for proper execution of the reaction. ThenableJobSlot_JobData, }; enum ThenableJobDataIndices { // The Promise to resolve using the given thenable. ThenableJobDataIndex_Promise = 0, // The thenable to use as the receiver when calling the `then` function. ThenableJobDataIndex_Thenable, ThenableJobDataLength, }; enum BuiltinThenableJobSlots { // The Promise to resolve using the given thenable. BuiltinThenableJobSlot_Promise = 0, // The thenable to use as the receiver when calling the built-in `then` // function. BuiltinThenableJobSlot_Thenable, }; enum PromiseAllDataHolderSlots { PromiseAllDataHolderSlot_Promise = 0, PromiseAllDataHolderSlot_RemainingElements, PromiseAllDataHolderSlot_ValuesArray, PromiseAllDataHolderSlot_ResolveFunction, PromiseAllDataHolderSlots, }; struct PromiseCapability { JSObject* promise = nullptr; JSObject* resolve = nullptr; JSObject* reject = nullptr; PromiseCapability() = default; static void trace(PromiseCapability* self, JSTracer* trc) { self->trace(trc); } void trace(JSTracer* trc); }; void PromiseCapability::trace(JSTracer* trc) { if (promise) TraceRoot(trc, &promise, "PromiseCapability::promise"); if (resolve) TraceRoot(trc, &resolve, "PromiseCapability::resolve"); if (reject) TraceRoot(trc, &reject, "PromiseCapability::reject"); } namespace js { template class WrappedPtrOperations { const PromiseCapability& capability() const { return static_cast(this)->get(); } public: HandleObject promise() const { return HandleObject::fromMarkedLocation(&capability().promise); } HandleObject resolve() const { return HandleObject::fromMarkedLocation(&capability().resolve); } HandleObject reject() const { return HandleObject::fromMarkedLocation(&capability().reject); } }; template class MutableWrappedPtrOperations : public WrappedPtrOperations { PromiseCapability& capability() { return static_cast(this)->get(); } public: MutableHandleObject promise() { return MutableHandleObject::fromMarkedLocation(&capability().promise); } MutableHandleObject resolve() { return MutableHandleObject::fromMarkedLocation(&capability().resolve); } MutableHandleObject reject() { return MutableHandleObject::fromMarkedLocation(&capability().reject); } }; } // namespace js class PromiseAllDataHolder : public NativeObject { public: static const Class class_; JSObject* promiseObj() { return &getFixedSlot(PromiseAllDataHolderSlot_Promise).toObject(); } JSObject* resolveObj() { return &getFixedSlot(PromiseAllDataHolderSlot_ResolveFunction).toObject(); } Value valuesArray() { return getFixedSlot(PromiseAllDataHolderSlot_ValuesArray); } int32_t remainingCount() { return getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32(); } int32_t increaseRemainingCount() { int32_t remainingCount = getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32(); remainingCount++; setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(remainingCount)); return remainingCount; } int32_t decreaseRemainingCount() { int32_t remainingCount = getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32(); remainingCount--; setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(remainingCount)); return remainingCount; } }; const Class PromiseAllDataHolder::class_ = { "PromiseAllDataHolder", JSCLASS_HAS_RESERVED_SLOTS(PromiseAllDataHolderSlots) }; static PromiseAllDataHolder* NewPromiseAllDataHolder(JSContext* cx, HandleObject resultPromise, HandleValue valuesArray, HandleObject resolve) { PromiseAllDataHolder* dataHolder = NewBuiltinClassInstance(cx); if (!dataHolder) return nullptr; cx->check(resultPromise); cx->check(valuesArray); cx->check(resolve); dataHolder->setFixedSlot(PromiseAllDataHolderSlot_Promise, ObjectValue(*resultPromise)); dataHolder->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(1)); dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ValuesArray, valuesArray); dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ResolveFunction, ObjectValue(*resolve)); return dataHolder; } namespace { // Generator used by PromiseObject::getID. mozilla::Atomic gIDGenerator(0); } // namespace static MOZ_ALWAYS_INLINE bool ShouldCaptureDebugInfo(JSContext* cx) { return cx->options().asyncStack() || cx->realm()->isDebuggee(); } static mozilla::Maybe MaybeNow() { // ShouldCaptureDebugInfo() may return inconsistent values when recording // or replaying, so in places where we might need the current time for // promise debug info we always capture the current time. if (mozilla::recordreplay::IsRecordingOrReplaying()) return mozilla::Some(mozilla::TimeStamp::Now()); return mozilla::Nothing(); } class PromiseDebugInfo : public NativeObject { private: enum Slots { Slot_AllocationSite, Slot_ResolutionSite, Slot_AllocationTime, Slot_ResolutionTime, Slot_Id, SlotCount }; public: static const Class class_; static PromiseDebugInfo* create(JSContext* cx, Handle promise, const mozilla::Maybe& maybeNow) { Rooted debugInfo(cx, NewBuiltinClassInstance(cx)); if (!debugInfo) return nullptr; RootedObject stack(cx); if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames()))) return nullptr; debugInfo->setFixedSlot(Slot_AllocationSite, ObjectOrNullValue(stack)); debugInfo->setFixedSlot(Slot_ResolutionSite, NullValue()); debugInfo->setFixedSlot(Slot_AllocationTime, DoubleValue(MillisecondsSinceStartup(maybeNow))); debugInfo->setFixedSlot(Slot_ResolutionTime, NumberValue(0)); promise->setFixedSlot(PromiseSlot_DebugInfo, ObjectValue(*debugInfo)); return debugInfo; } static PromiseDebugInfo* FromPromise(PromiseObject* promise) { Value val = promise->getFixedSlot(PromiseSlot_DebugInfo); if (val.isObject()) return &val.toObject().as(); return nullptr; } /** * Returns the given PromiseObject's process-unique ID. * The ID is lazily assigned when first queried, and then either stored * in the DebugInfo slot if no debug info was recorded for this Promise, * or in the Id slot of the DebugInfo object. */ static uint64_t id(PromiseObject* promise) { Value idVal(promise->getFixedSlot(PromiseSlot_DebugInfo)); if (idVal.isUndefined()) { idVal.setDouble(++gIDGenerator); promise->setFixedSlot(PromiseSlot_DebugInfo, idVal); } else if (idVal.isObject()) { PromiseDebugInfo* debugInfo = FromPromise(promise); idVal = debugInfo->getFixedSlot(Slot_Id); if (idVal.isUndefined()) { idVal.setDouble(++gIDGenerator); debugInfo->setFixedSlot(Slot_Id, idVal); } } return uint64_t(idVal.toNumber()); } double allocationTime() { return getFixedSlot(Slot_AllocationTime).toNumber(); } double resolutionTime() { return getFixedSlot(Slot_ResolutionTime).toNumber(); } JSObject* allocationSite() { return getFixedSlot(Slot_AllocationSite).toObjectOrNull(); } JSObject* resolutionSite() { return getFixedSlot(Slot_ResolutionSite).toObjectOrNull(); } static void setResolutionInfo(JSContext* cx, Handle promise) { mozilla::Maybe maybeNow = MaybeNow(); if (!ShouldCaptureDebugInfo(cx)) return; mozilla::recordreplay::AutoDisallowThreadEvents disallow; // If async stacks weren't enabled and the Promise's global wasn't a // debuggee when the Promise was created, we won't have a debugInfo // object. We still want to capture the resolution stack, so we // create the object now and change it's slots' values around a bit. Rooted debugInfo(cx, FromPromise(promise)); if (!debugInfo) { RootedValue idVal(cx, promise->getFixedSlot(PromiseSlot_DebugInfo)); debugInfo = create(cx, promise, maybeNow); if (!debugInfo) { cx->clearPendingException(); return; } // The current stack was stored in the AllocationSite slot, move // it to ResolutionSite as that's what it really is. debugInfo->setFixedSlot(Slot_ResolutionSite, debugInfo->getFixedSlot(Slot_AllocationSite)); debugInfo->setFixedSlot(Slot_AllocationSite, NullValue()); // There's no good default for a missing AllocationTime, so // instead of resetting that, ensure that it's the same as // ResolutionTime, so that the diff shows as 0, which isn't great, // but bearable. debugInfo->setFixedSlot(Slot_ResolutionTime, debugInfo->getFixedSlot(Slot_AllocationTime)); // The Promise's ID might've been queried earlier, in which case // it's stored in the DebugInfo slot. We saved that earlier, so // now we can store it in the right place (or leave it as // undefined if it wasn't ever initialized.) debugInfo->setFixedSlot(Slot_Id, idVal); return; } RootedObject stack(cx); if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames()))) { cx->clearPendingException(); return; } debugInfo->setFixedSlot(Slot_ResolutionSite, ObjectOrNullValue(stack)); debugInfo->setFixedSlot(Slot_ResolutionTime, DoubleValue(MillisecondsSinceStartup(maybeNow))); } }; const Class PromiseDebugInfo::class_ = { "PromiseDebugInfo", JSCLASS_HAS_RESERVED_SLOTS(SlotCount) }; double PromiseObject::allocationTime() { auto debugInfo = PromiseDebugInfo::FromPromise(this); if (debugInfo) return debugInfo->allocationTime(); return 0; } double PromiseObject::resolutionTime() { auto debugInfo = PromiseDebugInfo::FromPromise(this); if (debugInfo) return debugInfo->resolutionTime(); return 0; } JSObject* PromiseObject::allocationSite() { auto debugInfo = PromiseDebugInfo::FromPromise(this); if (debugInfo) return debugInfo->allocationSite(); return nullptr; } JSObject* PromiseObject::resolutionSite() { auto debugInfo = PromiseDebugInfo::FromPromise(this); if (debugInfo) return debugInfo->resolutionSite(); return nullptr; } /** * Wrapper for GetAndClearException that handles cases where no exception is * pending, but an error occurred. This can be the case if an OOM was * encountered while throwing the error. */ static bool MaybeGetAndClearException(JSContext* cx, MutableHandleValue rval) { if (!cx->isExceptionPending()) return false; return GetAndClearException(cx, rval); } static MOZ_MUST_USE bool RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue result, ResolutionMode mode, HandleObject promiseObj); // ES2016, 25.4.1.1.1, Steps 1.a-b. // Extracting all of this internal spec algorithm into a helper function would // be tedious, so the check in step 1 and the entirety of step 2 aren't // included. static bool AbruptRejectPromise(JSContext* cx, CallArgs& args, HandleObject promiseObj, HandleObject reject) { // Step 1.a. RootedValue reason(cx); if (!MaybeGetAndClearException(cx, &reason)) return false; if (!RunResolutionFunction(cx, reject, reason, RejectMode, promiseObj)) return false; // Step 1.b. args.rval().setObject(*promiseObj); return true; } static bool AbruptRejectPromise(JSContext* cx, CallArgs& args, Handle capability) { return AbruptRejectPromise(cx, args, capability.promise(), capability.reject()); } enum ReactionRecordSlots { // The promise for which this record provides a reaction handler. // Matches the [[Capability]].[[Promise]] field from the spec. // // The slot value is either an object, but not necessarily a built-in // Promise object, or null. The latter case is only possible for async // generator functions, in which case the REACTION_FLAG_ASYNC_GENERATOR // flag must be set. ReactionRecordSlot_Promise = 0, // The [[Handler]] field(s) of a PromiseReaction record. We create a // single reaction record for fulfillment and rejection, therefore our // PromiseReaction implementation needs two [[Handler]] fields. // // The slot value is either a callable object, an integer constant from // the |PromiseHandler| enum, or null. If the value is null, either the // REACTION_FLAG_DEBUGGER_DUMMY or the // REACTION_FLAG_DEFAULT_RESOLVING_HANDLER flag must be set. // // After setting the target state for a PromiseReaction, the slot of the // no longer used handler gets reused to store the argument of the active // handler. ReactionRecordSlot_OnFulfilled, ReactionRecordSlot_OnRejectedArg = ReactionRecordSlot_OnFulfilled, ReactionRecordSlot_OnRejected, ReactionRecordSlot_OnFulfilledArg = ReactionRecordSlot_OnRejected, // The functions to resolve or reject the promise. Matches the // [[Capability]].[[Resolve]] and [[Capability]].[[Reject]] fields from // the spec. // // The slot values are either callable objects or null, but the latter // case is only allowed if the promise is either a built-in Promise object // or null. ReactionRecordSlot_Resolve, ReactionRecordSlot_Reject, // The incumbent global for this reaction record. Can be null. ReactionRecordSlot_IncumbentGlobalObject, // Bitmask of the REACTION_FLAG values. ReactionRecordSlot_Flags, // Additional slot to store extra data for specific reaction record types. // // - When the REACTION_FLAG_ASYNC_GENERATOR flag is set, this slot store // the async generator function for this promise reaction. // - When the REACTION_FLAG_DEFAULT_RESOLVING_HANDLER flag is set, this // slot stores the promise to resolve when conceptually "calling" the // OnFulfilled or OnRejected handlers. ReactionRecordSlot_GeneratorOrPromiseToResolve, ReactionRecordSlots, }; // ES2016, 25.4.1.2. class PromiseReactionRecord : public NativeObject { static constexpr uint32_t REACTION_FLAG_RESOLVED = 0x1; static constexpr uint32_t REACTION_FLAG_FULFILLED = 0x2; static constexpr uint32_t REACTION_FLAG_DEFAULT_RESOLVING_HANDLER = 0x4; static constexpr uint32_t REACTION_FLAG_ASYNC_FUNCTION = 0x8; static constexpr uint32_t REACTION_FLAG_ASYNC_GENERATOR = 0x10; static constexpr uint32_t REACTION_FLAG_DEBUGGER_DUMMY = 0x20; void setFlagOnInitialState(uint32_t flag) { int32_t flags = this->flags(); MOZ_ASSERT(flags == 0, "Can't modify with non-default flags"); flags |= flag; setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); } uint32_t handlerSlot() { MOZ_ASSERT(targetState() != JS::PromiseState::Pending); return targetState() == JS::PromiseState::Fulfilled ? ReactionRecordSlot_OnFulfilled : ReactionRecordSlot_OnRejected; } uint32_t handlerArgSlot() { MOZ_ASSERT(targetState() != JS::PromiseState::Pending); return targetState() == JS::PromiseState::Fulfilled ? ReactionRecordSlot_OnFulfilledArg : ReactionRecordSlot_OnRejectedArg; } public: static const Class class_; JSObject* promise() { return getFixedSlot(ReactionRecordSlot_Promise).toObjectOrNull(); } int32_t flags() { return getFixedSlot(ReactionRecordSlot_Flags).toInt32(); } JS::PromiseState targetState() { int32_t flags = this->flags(); if (!(flags & REACTION_FLAG_RESOLVED)) return JS::PromiseState::Pending; return flags & REACTION_FLAG_FULFILLED ? JS::PromiseState::Fulfilled : JS::PromiseState::Rejected; } void setTargetStateAndHandlerArg(JS::PromiseState state, const Value& arg) { MOZ_ASSERT(targetState() == JS::PromiseState::Pending); MOZ_ASSERT(state != JS::PromiseState::Pending, "Can't revert a reaction to pending."); int32_t flags = this->flags(); flags |= REACTION_FLAG_RESOLVED; if (state == JS::PromiseState::Fulfilled) flags |= REACTION_FLAG_FULFILLED; setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); setFixedSlot(handlerArgSlot(), arg); } void setIsDefaultResolvingHandler(PromiseObject* promiseToResolve) { setFlagOnInitialState(REACTION_FLAG_DEFAULT_RESOLVING_HANDLER); setFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve, ObjectValue(*promiseToResolve)); } bool isDefaultResolvingHandler() { int32_t flags = this->flags(); return flags & REACTION_FLAG_DEFAULT_RESOLVING_HANDLER; } PromiseObject* defaultResolvingPromise() { MOZ_ASSERT(isDefaultResolvingHandler()); const Value& promiseToResolve = getFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve); return &promiseToResolve.toObject().as(); } void setIsAsyncFunction() { setFlagOnInitialState(REACTION_FLAG_ASYNC_FUNCTION); } bool isAsyncFunction() { int32_t flags = this->flags(); return flags & REACTION_FLAG_ASYNC_FUNCTION; } void setIsAsyncGenerator(AsyncGeneratorObject* asyncGenObj) { setFlagOnInitialState(REACTION_FLAG_ASYNC_GENERATOR); setFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve, ObjectValue(*asyncGenObj)); } bool isAsyncGenerator() { int32_t flags = this->flags(); return flags & REACTION_FLAG_ASYNC_GENERATOR; } AsyncGeneratorObject* asyncGenerator() { MOZ_ASSERT(isAsyncGenerator()); const Value& generator = getFixedSlot(ReactionRecordSlot_GeneratorOrPromiseToResolve); return &generator.toObject().as(); } void setIsDebuggerDummy() { setFlagOnInitialState(REACTION_FLAG_DEBUGGER_DUMMY); } bool isDebuggerDummy() { int32_t flags = this->flags(); return flags & REACTION_FLAG_DEBUGGER_DUMMY; } Value handler() { MOZ_ASSERT(targetState() != JS::PromiseState::Pending); return getFixedSlot(handlerSlot()); } Value handlerArg() { MOZ_ASSERT(targetState() != JS::PromiseState::Pending); return getFixedSlot(handlerArgSlot()); } JSObject* getAndClearIncumbentGlobalObject() { JSObject* obj = getFixedSlot(ReactionRecordSlot_IncumbentGlobalObject).toObjectOrNull(); setFixedSlot(ReactionRecordSlot_IncumbentGlobalObject, UndefinedValue()); return obj; } }; const Class PromiseReactionRecord::class_ = { "PromiseReactionRecord", JSCLASS_HAS_RESERVED_SLOTS(ReactionRecordSlots) }; static void AddPromiseFlags(PromiseObject& promise, int32_t flag) { int32_t flags = promise.flags(); promise.setFixedSlot(PromiseSlot_Flags, Int32Value(flags | flag)); } static bool PromiseHasAnyFlag(PromiseObject& promise, int32_t flag) { return promise.flags() & flag; } static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp); static bool RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp); // ES2016, 25.4.1.3. static MOZ_MUST_USE MOZ_ALWAYS_INLINE bool CreateResolvingFunctions(JSContext* cx, HandleObject promise, MutableHandleObject resolveFn, MutableHandleObject rejectFn) { HandlePropertyName funName = cx->names().empty; resolveFn.set(NewNativeFunction(cx, ResolvePromiseFunction, 1, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!resolveFn) return false; rejectFn.set(NewNativeFunction(cx, RejectPromiseFunction, 1, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!rejectFn) return false; JSFunction* resolveFun = &resolveFn->as(); JSFunction* rejectFun = &rejectFn->as(); resolveFun->initExtendedSlot(ResolveFunctionSlot_Promise, ObjectValue(*promise)); resolveFun->initExtendedSlot(ResolveFunctionSlot_RejectFunction, ObjectValue(*rejectFun)); rejectFun->initExtendedSlot(RejectFunctionSlot_Promise, ObjectValue(*promise)); rejectFun->initExtendedSlot(RejectFunctionSlot_ResolveFunction, ObjectValue(*resolveFun)); return true; } static void ClearResolutionFunctionSlots(JSFunction* resolutionFun); static bool IsSettledMaybeWrappedPromise(JSObject* promise) { if (IsProxy(promise)) { promise = UncheckedUnwrap(promise); // Caller needs to handle dead wrappers. if (JS_IsDeadWrapper(promise)) return false; } return promise->as().state() != JS::PromiseState::Pending; } // ES2016, 25.4.1.7. static MOZ_MUST_USE bool RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue reason); // ES2016, 25.4.1.7. static MOZ_MUST_USE bool RejectPromiseInternal(JSContext* cx, Handle promise, HandleValue reason); // ES2016, 25.4.1.3.1. static bool RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); JSFunction* reject = &args.callee().as(); HandleValue reasonVal = args.get(0); // Steps 1-2. const Value& promiseVal = reject->getExtendedSlot(RejectFunctionSlot_Promise); // Steps 3-4. // If the Promise isn't available anymore, it has been resolved and the // reference to it removed to make it eligible for collection. if (promiseVal.isUndefined()) { args.rval().setUndefined(); return true; } // Store the promise value in |promise| before ClearResolutionFunctionSlots // removes the reference. RootedObject promise(cx, &promiseVal.toObject()); // Step 5. // Here, we only remove the Promise reference from the resolution // functions. Actually marking it as fulfilled/rejected happens later. ClearResolutionFunctionSlots(reject); // In some cases the Promise reference on the resolution function won't // have been removed during resolution, so we need to check that here, // too. if (IsSettledMaybeWrappedPromise(promise)) { args.rval().setUndefined(); return true; } // Step 6. if (!RejectMaybeWrappedPromise(cx, promise, reasonVal)) return false; args.rval().setUndefined(); return true; } static MOZ_MUST_USE bool FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue value_); static MOZ_MUST_USE bool EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve, HandleValue thenable, HandleValue thenVal); static MOZ_MUST_USE bool EnqueuePromiseResolveThenableBuiltinJob(JSContext* cx, HandleObject promiseToResolve, HandleObject thenable); static bool Promise_then(JSContext* cx, unsigned argc, Value* vp); static bool Promise_then_impl(JSContext* cx, HandleValue promiseVal, HandleValue onFulfilled, HandleValue onRejected, MutableHandleValue rval, bool rvalUsed); // ES2016, 25.4.1.3.2, steps 6-13. static MOZ_MUST_USE bool ResolvePromiseInternal(JSContext* cx, HandleObject promise, HandleValue resolutionVal) { cx->check(promise, resolutionVal); MOZ_ASSERT(!IsSettledMaybeWrappedPromise(promise)); // Step 7 (reordered). if (!resolutionVal.isObject()) return FulfillMaybeWrappedPromise(cx, promise, resolutionVal); RootedObject resolution(cx, &resolutionVal.toObject()); // Step 6. if (resolution == promise) { // Step 6.a. JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF); RootedValue selfResolutionError(cx); if (!MaybeGetAndClearException(cx, &selfResolutionError)) return false; // Step 6.b. return RejectMaybeWrappedPromise(cx, promise, selfResolutionError); } // Step 8. RootedValue thenVal(cx); bool status = GetProperty(cx, resolution, resolution, cx->names().then, &thenVal); RootedValue error(cx); if (!status) { if (!MaybeGetAndClearException(cx, &error)) return false; } // Testing functions allow to directly settle a promise without going // through the resolving functions. In that case the normal bookkeeping to // ensure only pending promises can be resolved doesn't apply and we need // to manually check for already settled promises. The exception is simply // dropped when this case happens. if (IsSettledMaybeWrappedPromise(promise)) return true; // Step 9. if (!status) return RejectMaybeWrappedPromise(cx, promise, error); // Step 10 (implicit). // Step 11. if (!IsCallable(thenVal)) return FulfillMaybeWrappedPromise(cx, promise, resolutionVal); // If the resolution object is a built-in Promise object and the // `then` property is the original Promise.prototype.then function // from the current realm, we skip storing/calling it. // Additionally we require that |promise| itself is also a built-in // Promise object, so the fast path doesn't need to cope with wrappers. bool isBuiltinThen = false; if (resolution->is() && promise->is() && IsNativeFunction(thenVal, Promise_then) && thenVal.toObject().as().realm() == cx->realm()) { isBuiltinThen = true; } // Step 12. if (!isBuiltinThen) { RootedValue promiseVal(cx, ObjectValue(*promise)); if (!EnqueuePromiseResolveThenableJob(cx, promiseVal, resolutionVal, thenVal)) return false; } else { if (!EnqueuePromiseResolveThenableBuiltinJob(cx, promise, resolution)) return false; } // Step 13. return true; } // ES2016, 25.4.1.3.2. static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); JSFunction* resolve = &args.callee().as(); HandleValue resolutionVal = args.get(0); // Steps 3-4 (reordered). // We use the reference to the reject function as a signal for whether // the resolve or reject function was already called, at which point // the references on each of the functions are cleared. if (!resolve->getExtendedSlot(ResolveFunctionSlot_RejectFunction).isObject()) { args.rval().setUndefined(); return true; } // Steps 1-2 (reordered). RootedObject promise(cx, &resolve->getExtendedSlot(ResolveFunctionSlot_Promise).toObject()); // Step 5. // Here, we only remove the Promise reference from the resolution // functions. Actually marking it as fulfilled/rejected happens later. ClearResolutionFunctionSlots(resolve); // In some cases the Promise reference on the resolution function won't // have been removed during resolution, so we need to check that here, // too. if (IsSettledMaybeWrappedPromise(promise)) { args.rval().setUndefined(); return true; } // Steps 6-13. if (!ResolvePromiseInternal(cx, promise, resolutionVal)) return false; args.rval().setUndefined(); return true; } static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp); /** * Tells the embedding to enqueue a Promise reaction job, based on * three parameters: * reactionObj - The reaction record. * handlerArg_ - The first and only argument to pass to the handler invoked by * the job. This will be stored on the reaction record. * targetState - The PromiseState this reaction job targets. This decides * whether the onFulfilled or onRejected handler is called. */ MOZ_MUST_USE static bool EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj, HandleValue handlerArg_, JS::PromiseState targetState) { MOZ_ASSERT(targetState == JS::PromiseState::Fulfilled || targetState == JS::PromiseState::Rejected); // The reaction might have been stored on a Promise from another // compartment, which means it would've been wrapped in a CCW. // To properly handle that case here, unwrap it and enter its // compartment, where the job creation should take place anyway. Rooted reaction(cx); RootedValue handlerArg(cx, handlerArg_); mozilla::Maybe ar; if (!IsProxy(reactionObj)) { MOZ_RELEASE_ASSERT(reactionObj->is()); reaction = &reactionObj->as(); } else { JSObject* unwrappedReactionObj = UncheckedUnwrap(reactionObj); if (JS_IsDeadWrapper(unwrappedReactionObj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } reaction = &unwrappedReactionObj->as(); MOZ_RELEASE_ASSERT(reaction->is()); ar.emplace(cx, reaction); if (!cx->compartment()->wrap(cx, &handlerArg)) return false; } // Must not enqueue a reaction job more than once. MOZ_ASSERT(reaction->targetState() == JS::PromiseState::Pending); cx->check(handlerArg); reaction->setTargetStateAndHandlerArg(targetState, handlerArg); RootedValue reactionVal(cx, ObjectValue(*reaction)); RootedValue handler(cx, reaction->handler()); // If we have a handler callback, we enter that handler's compartment so // that the promise reaction job function is created in that compartment. // That guarantees that the embedding ends up with the right entry global. // This is relevant for some html APIs like fetch that derive information // from said global. mozilla::Maybe ar2; if (handler.isObject()) { // The unwrapping has to be unchecked because we specifically want to // be able to use handlers with wrappers that would only allow calls. // E.g., it's ok to have a handler from a chrome compartment in a // reaction to a content compartment's Promise instance. JSObject* handlerObj = UncheckedUnwrap(&handler.toObject()); MOZ_ASSERT(handlerObj); ar2.emplace(cx, handlerObj); // We need to wrap the reaction to store it on the job function. if (!cx->compartment()->wrap(cx, &reactionVal)) return false; } // Create the JS function to call when the job is triggered. HandlePropertyName funName = cx->names().empty; RootedFunction job(cx, NewNativeFunction(cx, PromiseReactionJob, 0, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!job) return false; // Store the reaction on the reaction job. job->setExtendedSlot(ReactionJobSlot_ReactionRecord, reactionVal); // When using JS::AddPromiseReactions, no actual promise is created, so we // might not have one here. // Additionally, we might have an object here that isn't an instance of // Promise. This can happen if content overrides the value of // Promise[@@species] (or invokes Promise#then on a Promise subclass // instance with a non-default @@species value on the constructor) with a // function that returns objects that're not Promise (subclass) instances. // In that case, we just pretend we didn't have an object in the first // place. // If after all this we do have an object, wrap it in case we entered the // handler's compartment above, because we should pass objects from a // single compartment to the enqueuePromiseJob callback. RootedObject promise(cx, reaction->promise()); if (promise && promise->is()) { if (!cx->compartment()->wrap(cx, &promise)) return false; } // Using objectFromIncumbentGlobal, we can derive the incumbent global by // unwrapping and then getting the global. This is very convoluted, but // much better than having to store the original global as a private value // because we couldn't wrap it to store it as a normal JS value. Rooted global(cx); if (JSObject* objectFromIncumbentGlobal = reaction->getAndClearIncumbentGlobalObject()) { objectFromIncumbentGlobal = CheckedUnwrap(objectFromIncumbentGlobal); MOZ_ASSERT(objectFromIncumbentGlobal); global = &objectFromIncumbentGlobal->nonCCWGlobal(); } // Note: the global we pass here might be from a different compartment // than job and promise. While it's somewhat unusual to pass objects // from multiple compartments, in this case we specifically need the // global to be unwrapped because wrapping and unwrapping aren't // necessarily symmetric for globals. return cx->runtime()->enqueuePromiseJob(cx, job, promise, global); } static MOZ_MUST_USE bool TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseState state, HandleValue valueOrReason); // ES2016, Commoned-out implementation of 25.4.1.4. and 25.4.1.7. static MOZ_MUST_USE bool ResolvePromise(JSContext* cx, Handle promise, HandleValue valueOrReason, JS::PromiseState state) { // Step 1. MOZ_ASSERT(promise->state() == JS::PromiseState::Pending); MOZ_ASSERT(state == JS::PromiseState::Fulfilled || state == JS::PromiseState::Rejected); // Step 2. // We only have one list of reactions for both resolution types. So // instead of getting the right list of reactions, we determine the // resolution type to retrieve the right information from the // reaction records. RootedValue reactionsVal(cx, promise->reactions()); // Steps 3-5. // The same slot is used for the reactions list and the result, so setting // the result also removes the reactions list. promise->setFixedSlot(PromiseSlot_ReactionsOrResult, valueOrReason); // Step 6. int32_t flags = promise->flags(); flags |= PROMISE_FLAG_RESOLVED; if (state == JS::PromiseState::Fulfilled) flags |= PROMISE_FLAG_FULFILLED; promise->setFixedSlot(PromiseSlot_Flags, Int32Value(flags)); // Also null out the resolve/reject functions so they can be GC'd. promise->setFixedSlot(PromiseSlot_RejectFunction, UndefinedValue()); // Now that everything else is done, do the things the debugger needs. // Step 7 of RejectPromise implemented in onSettled. PromiseObject::onSettled(cx, promise); // Step 7 of FulfillPromise. // Step 8 of RejectPromise. if (reactionsVal.isObject()) return TriggerPromiseReactions(cx, reactionsVal, state, valueOrReason); return true; } // ES2016, 25.4.1.7. static MOZ_MUST_USE bool RejectPromiseInternal(JSContext* cx, Handle promise, HandleValue reason) { return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected); } // ES2016, 25.4.1.4. static MOZ_MUST_USE bool FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue value_) { Rooted promise(cx); RootedValue value(cx, value_); mozilla::Maybe ar; if (!IsProxy(promiseObj)) { promise = &promiseObj->as(); } else { JSObject* unwrappedPromiseObj = UncheckedUnwrap(promiseObj); if (JS_IsDeadWrapper(unwrappedPromiseObj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } promise = &unwrappedPromiseObj->as(); ar.emplace(cx, promise); if (!cx->compartment()->wrap(cx, &value)) return false; } return ResolvePromise(cx, promise, value, JS::PromiseState::Fulfilled); } static bool GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp); static bool PromiseConstructor(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE PromiseObject* CreatePromiseObjectInternal(JSContext* cx, HandleObject proto = nullptr, bool protoIsWrapped = false, bool informDebugger = true); enum GetCapabilitiesExecutorSlots { GetCapabilitiesExecutorSlots_Resolve, GetCapabilitiesExecutorSlots_Reject }; static MOZ_MUST_USE PromiseObject* CreatePromiseObjectWithoutResolutionFunctions(JSContext* cx) { PromiseObject* promise = CreatePromiseObjectInternal(cx); if (!promise) return nullptr; AddPromiseFlags(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS); return promise; } static MOZ_MUST_USE PromiseObject* CreatePromiseWithDefaultResolutionFunctions(JSContext* cx, MutableHandleObject resolve, MutableHandleObject reject) { // ES2016, 25.4.3.1., as if called with GetCapabilitiesExecutor as the // executor argument. // Steps 1-2 (Not applicable). // Steps 3-7. Rooted promise(cx, CreatePromiseObjectInternal(cx)); if (!promise) return nullptr; // Step 8. if (!CreateResolvingFunctions(cx, promise, resolve, reject)) return nullptr; promise->setFixedSlot(PromiseSlot_RejectFunction, ObjectValue(*reject)); // Steps 9-10 (Not applicable). // Step 11. return promise; } // ES2016, 25.4.1.5. static MOZ_MUST_USE bool NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandle capability, bool canOmitResolutionFunctions) { RootedValue cVal(cx, ObjectValue(*C)); // Steps 1-2. if (!IsConstructor(C)) { ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_SEARCH_STACK, cVal, nullptr); return false; } // If we'd call the original Promise constructor and know that the // resolve/reject functions won't ever escape to content, we can skip // creating and calling the executor function and instead return a Promise // marked as having default resolve/reject functions. // // This can't be used in Promise.all and Promise.race because we have to // pass the reject (and resolve, in the race case) function to thenables // in the list passed to all/race, which (potentially) means exposing them // to content. // // For Promise.all and Promise.race we can only optimize away the creation // of the GetCapabilitiesExecutor function, and directly allocate the // result promise instead of invoking the Promise constructor. if (IsNativeFunction(cVal, PromiseConstructor)) { PromiseObject* promise; if (canOmitResolutionFunctions) { promise = CreatePromiseObjectWithoutResolutionFunctions(cx); } else { promise = CreatePromiseWithDefaultResolutionFunctions(cx, capability.resolve(), capability.reject()); } if (!promise) return false; capability.promise().set(promise); return true; } // Step 3 (omitted). // Step 4. HandlePropertyName funName = cx->names().empty; RootedFunction executor(cx, NewNativeFunction(cx, GetCapabilitiesExecutor, 2, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!executor) return false; // Step 5 (omitted). // Step 6. FixedConstructArgs<1> cargs(cx); cargs[0].setObject(*executor); if (!Construct(cx, cVal, cargs, cVal, capability.promise())) return false; // Step 7. const Value& resolveVal = executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve); if (!IsCallable(resolveVal)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE); return false; } // Step 8. const Value& rejectVal = executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject); if (!IsCallable(rejectVal)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE); return false; } // Step 9 (well, the equivalent for all of promiseCapabilities' fields.) capability.resolve().set(&resolveVal.toObject()); capability.reject().set(&rejectVal.toObject()); // Step 10. return true; } // ES2016, 25.4.1.5.1. static bool GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); JSFunction* F = &args.callee().as(); // Steps 1-2 (implicit). // Steps 3-4. if (!F->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve).isUndefined() || !F->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject).isUndefined()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY); return false; } // Step 5. F->setExtendedSlot(GetCapabilitiesExecutorSlots_Resolve, args.get(0)); // Step 6. F->setExtendedSlot(GetCapabilitiesExecutorSlots_Reject, args.get(1)); // Step 7. args.rval().setUndefined(); return true; } // ES2016, 25.4.1.7. static MOZ_MUST_USE bool RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue reason_) { Rooted promise(cx); RootedValue reason(cx, reason_); mozilla::Maybe ar; if (!IsProxy(promiseObj)) { promise = &promiseObj->as(); } else { JSObject* unwrappedPromiseObj = UncheckedUnwrap(promiseObj); if (JS_IsDeadWrapper(unwrappedPromiseObj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } promise = &unwrappedPromiseObj->as(); ar.emplace(cx, promise); // The rejection reason might've been created in a compartment with higher // privileges than the Promise's. In that case, object-type rejection // values might be wrapped into a wrapper that throws whenever the // Promise's reaction handler wants to do anything useful with it. To // avoid that situation, we synthesize a generic error that doesn't // expose any privileged information but can safely be used in the // rejection handler. if (!cx->compartment()->wrap(cx, &reason)) return false; if (reason.isObject() && !CheckedUnwrap(&reason.toObject())) { // Report the existing reason, so we don't just drop it on the // floor. JSObject* realReason = UncheckedUnwrap(&reason.toObject()); RootedValue realReasonVal(cx, ObjectValue(*realReason)); Rooted realGlobal(cx, &realReason->nonCCWGlobal()); ReportErrorToGlobal(cx, realGlobal, realReasonVal); // Async stacks are only properly adopted if there's at least one // interpreter frame active right now. If a thenable job with a // throwing `then` function got us here, that'll not be the case, // so we add one by throwing the error from self-hosted code. if (!GetInternalError(cx, JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON, &reason)) return false; } } return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected); } // ES2016, 25.4.1.8. static MOZ_MUST_USE bool TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseState state, HandleValue valueOrReason) { MOZ_ASSERT(state == JS::PromiseState::Fulfilled || state == JS::PromiseState::Rejected); RootedObject reactions(cx, &reactionsVal.toObject()); if (reactions->is() || IsWrapper(reactions) || JS_IsDeadWrapper(reactions)) { return EnqueuePromiseReactionJob(cx, reactions, valueOrReason, state); } HandleNativeObject reactionsList = reactions.as(); uint32_t reactionsCount = reactionsList->getDenseInitializedLength(); MOZ_ASSERT(reactionsCount > 1, "Reactions list should be created lazily"); RootedObject reaction(cx); for (uint32_t i = 0; i < reactionsCount; i++) { const Value& reactionVal = reactionsList->getDenseElement(i); MOZ_RELEASE_ASSERT(reactionVal.isObject()); reaction = &reactionVal.toObject(); if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state)) return false; } return true; } // Implements PromiseReactionJob optimized for the case when the reaction // handler is one of the default resolving functions as created by the // CreateResolvingFunctions abstract operation. static MOZ_MUST_USE bool DefaultResolvingPromiseReactionJob(JSContext* cx, Handle reaction, MutableHandleValue rval) { MOZ_ASSERT(reaction->targetState() != JS::PromiseState::Pending); Rooted promiseToResolve(cx, reaction->defaultResolvingPromise()); // Testing functions allow to directly settle a promise without going // through the resolving functions. In that case the normal bookkeeping to // ensure only pending promises can be resolved doesn't apply and we need // to manually check for already settled promises. We still call // RunResolutionFunction for consistency with PromiseReactionJob. ResolutionMode resolutionMode = ResolveMode; RootedValue handlerResult(cx, UndefinedValue()); if (promiseToResolve->state() == JS::PromiseState::Pending) { RootedValue argument(cx, reaction->handlerArg()); // Step 6. bool ok; if (reaction->targetState() == JS::PromiseState::Fulfilled) ok = ResolvePromiseInternal(cx, promiseToResolve, argument); else ok = RejectPromiseInternal(cx, promiseToResolve, argument); if (!ok) { resolutionMode = RejectMode; if (!MaybeGetAndClearException(cx, &handlerResult)) return false; } } // Steps 7-9. uint32_t hookSlot = resolutionMode == RejectMode ? ReactionRecordSlot_Reject : ReactionRecordSlot_Resolve; RootedObject callee(cx, reaction->getFixedSlot(hookSlot).toObjectOrNull()); RootedObject promiseObj(cx, reaction->promise()); if (!RunResolutionFunction(cx, callee, handlerResult, resolutionMode, promiseObj)) return false; rval.setUndefined(); return true; } static MOZ_MUST_USE bool AsyncFunctionPromiseReactionJob(JSContext* cx, Handle reaction, MutableHandleValue rval) { MOZ_ASSERT(reaction->isAsyncFunction()); RootedValue handlerVal(cx, reaction->handler()); RootedValue argument(cx, reaction->handlerArg()); Rooted resultPromise(cx, &reaction->promise()->as()); RootedValue generatorVal(cx, resultPromise->getFixedSlot(PromiseSlot_AwaitGenerator)); int32_t handlerNum = handlerVal.toInt32(); // Await's handlers don't return a value, nor throw exception. // They fail only on OOM. if (handlerNum == PromiseHandlerAsyncFunctionAwaitedFulfilled) { if (!AsyncFunctionAwaitedFulfilled(cx, resultPromise, generatorVal, argument)) return false; } else { MOZ_ASSERT(handlerNum == PromiseHandlerAsyncFunctionAwaitedRejected); if (!AsyncFunctionAwaitedRejected(cx, resultPromise, generatorVal, argument)) return false; } rval.setUndefined(); return true; } static MOZ_MUST_USE bool AsyncGeneratorPromiseReactionJob(JSContext* cx, Handle reaction, MutableHandleValue rval) { MOZ_ASSERT(reaction->isAsyncGenerator()); RootedValue handlerVal(cx, reaction->handler()); RootedValue argument(cx, reaction->handlerArg()); Rooted asyncGenObj(cx, reaction->asyncGenerator()); int32_t handlerNum = handlerVal.toInt32(); // Await's handlers don't return a value, nor throw exception. // They fail only on OOM. if (handlerNum == PromiseHandlerAsyncGeneratorAwaitedFulfilled) { // 4.1.1. if (!AsyncGeneratorAwaitedFulfilled(cx, asyncGenObj, argument)) return false; } else if (handlerNum == PromiseHandlerAsyncGeneratorAwaitedRejected) { // 4.1.2. if (!AsyncGeneratorAwaitedRejected(cx, asyncGenObj, argument)) return false; } else if (handlerNum == PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled) { asyncGenObj->setCompleted(); // 11.4.3.5.1 step 1. if (!AsyncGeneratorResolve(cx, asyncGenObj, argument, true)) return false; } else if (handlerNum == PromiseHandlerAsyncGeneratorResumeNextReturnRejected) { asyncGenObj->setCompleted(); // 11.4.3.5.2 step 1. if (!AsyncGeneratorReject(cx, asyncGenObj, argument)) return false; } else if (handlerNum == PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled) { asyncGenObj->setExecuting(); // 11.4.3.7 steps 8.d-e. if (!AsyncGeneratorYieldReturnAwaitedFulfilled(cx, asyncGenObj, argument)) return false; } else { MOZ_ASSERT(handlerNum == PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected); asyncGenObj->setExecuting(); // 11.4.3.7 step 8.c. if (!AsyncGeneratorYieldReturnAwaitedRejected(cx, asyncGenObj, argument)) return false; } rval.setUndefined(); return true; } // ES2016, 25.4.2.1. /** * Callback triggering the fulfill/reject reaction for a resolved Promise, * to be invoked by the embedding during its processing of the Promise job * queue. * * See http://www.ecma-international.org/ecma-262/7.0/index.html#sec-jobs-and-job-queues * * A PromiseReactionJob is set as the native function of an extended * JSFunction object, with all information required for the job's * execution stored in in a reaction record in its first extended slot. */ static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedFunction job(cx, &args.callee().as()); RootedObject reactionObj(cx, &job->getExtendedSlot(ReactionJobSlot_ReactionRecord).toObject()); // To ensure that the embedding ends up with the right entry global, we're // guaranteeing that the reaction job function gets created in the same // compartment as the handler function. That's not necessarily the global // that the job was triggered from, though. // We can find the triggering global via the job's reaction record. To go // back, we check if the reaction is a wrapper and if so, unwrap it and // enter its compartment. mozilla::Maybe ar; if (!IsProxy(reactionObj)) { MOZ_RELEASE_ASSERT(reactionObj->is()); } else { reactionObj = UncheckedUnwrap(reactionObj); if (JS_IsDeadWrapper(reactionObj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } MOZ_RELEASE_ASSERT(reactionObj->is()); ar.emplace(cx, reactionObj); } // Steps 1-2. Handle reaction = reactionObj.as(); if (reaction->isDefaultResolvingHandler()) return DefaultResolvingPromiseReactionJob(cx, reaction, args.rval()); if (reaction->isAsyncFunction()) return AsyncFunctionPromiseReactionJob(cx, reaction, args.rval()); if (reaction->isAsyncGenerator()) return AsyncGeneratorPromiseReactionJob(cx, reaction, args.rval()); if (reaction->isDebuggerDummy()) return true; // Step 3. RootedValue handlerVal(cx, reaction->handler()); RootedValue argument(cx, reaction->handlerArg()); RootedValue handlerResult(cx); ResolutionMode resolutionMode = ResolveMode; // Steps 4-6. if (handlerVal.isInt32()) { int32_t handlerNum = handlerVal.toInt32(); // Step 4. if (handlerNum == PromiseHandlerIdentity) { handlerResult = argument; } else if (handlerNum == PromiseHandlerThrower) { // Step 5. resolutionMode = RejectMode; handlerResult = argument; } else { MOZ_ASSERT(handlerNum == PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone || handlerNum == PromiseHandlerAsyncFromSyncIteratorValueUnwrapNotDone); bool done = handlerNum == PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone; // Async Iteration proposal 11.1.3.2.5 step 1. JSObject* resultObj = CreateIterResultObject(cx, argument, done); if (!resultObj) return false; handlerResult = ObjectValue(*resultObj); } } else { MOZ_ASSERT(handlerVal.isObject()); MOZ_ASSERT(IsCallable(handlerVal)); // Step 6. if (!Call(cx, handlerVal, UndefinedHandleValue, argument, &handlerResult)) { resolutionMode = RejectMode; if (!MaybeGetAndClearException(cx, &handlerResult)) return false; } } // Steps 7-9. uint32_t hookSlot = resolutionMode == RejectMode ? ReactionRecordSlot_Reject : ReactionRecordSlot_Resolve; RootedObject callee(cx, reaction->getFixedSlot(hookSlot).toObjectOrNull()); RootedObject promiseObj(cx, reaction->promise()); if (!RunResolutionFunction(cx, callee, handlerResult, resolutionMode, promiseObj)) return false; args.rval().setUndefined(); return true; } // ES2016, 25.4.2.2. /** * Callback for resolving a thenable, to be invoked by the embedding during * its processing of the Promise job queue. * * See https://tc39.github.io/ecma262/#sec-jobs-and-job-queues * * A PromiseResolveThenableJob is set as the native function of an extended * JSFunction object, with all information required for the job's * execution stored in the function's extended slots. * * Usage of the function's extended slots is described in the ThenableJobSlots * enum. */ static bool PromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedFunction job(cx, &args.callee().as()); RootedValue then(cx, job->getExtendedSlot(ThenableJobSlot_Handler)); MOZ_ASSERT(then.isObject()); MOZ_ASSERT(!IsWrapper(&then.toObject())); RootedNativeObject jobArgs(cx, &job->getExtendedSlot(ThenableJobSlot_JobData) .toObject().as()); RootedObject promise(cx, &jobArgs->getDenseElement(ThenableJobDataIndex_Promise).toObject()); RootedValue thenable(cx, jobArgs->getDenseElement(ThenableJobDataIndex_Thenable)); // Step 1. RootedObject resolveFn(cx); RootedObject rejectFn(cx); if (!CreateResolvingFunctions(cx, promise, &resolveFn, &rejectFn)) return false; // Step 2. FixedInvokeArgs<2> args2(cx); args2[0].setObject(*resolveFn); args2[1].setObject(*rejectFn); // In difference to the usual pattern, we return immediately on success. RootedValue rval(cx); if (Call(cx, then, thenable, args2, &rval)) return true; // Steps 3-4. if (!MaybeGetAndClearException(cx, &rval)) return false; RootedValue rejectVal(cx, ObjectValue(*rejectFn)); return Call(cx, rejectVal, UndefinedHandleValue, rval, &rval); } static MOZ_MUST_USE bool OriginalPromiseThenWithoutSettleHandlers(JSContext* cx, Handle promise, Handle promiseToResolve); /** * Specialization of PromiseResolveThenableJob when the `thenable` is a * built-in Promise object and the `then` property is the built-in * `Promise.prototype.then` function. * * A PromiseResolveBuiltinThenableJob is set as the native function of an * extended JSFunction object, with all information required for the job's * execution stored in the function's extended slots. * * Usage of the function's extended slots is described in the * BuiltinThenableJobSlots enum. */ static bool PromiseResolveBuiltinThenableJob(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedFunction job(cx, &args.callee().as()); RootedObject promise(cx, &job->getExtendedSlot(BuiltinThenableJobSlot_Promise).toObject()); RootedObject thenable(cx, &job->getExtendedSlot(BuiltinThenableJobSlot_Thenable).toObject()); cx->check(promise, thenable); MOZ_ASSERT(promise->is()); MOZ_ASSERT(thenable->is()); // Step 1 (Skipped). // Step 2. // In difference to the usual pattern, we return immediately on success. if (OriginalPromiseThenWithoutSettleHandlers(cx, thenable.as(), promise.as())) { return true; } // Steps 3-4. RootedValue exception(cx); if (!MaybeGetAndClearException(cx, &exception)) return false; // Testing functions allow to directly settle a promise without going // through the resolving functions. In that case the normal bookkeeping to // ensure only pending promises can be resolved doesn't apply and we need // to manually check for already settled promises. The exception is simply // dropped when this case happens. if (promise->as().state() != JS::PromiseState::Pending) return true; return RejectPromiseInternal(cx, promise.as(), exception); } /** * Tells the embedding to enqueue a Promise resolve thenable job, based on * three parameters: * promiseToResolve_ - The promise to resolve, obviously. * thenable_ - The thenable to resolve the Promise with. * thenVal - The `then` function to invoke with the `thenable` as the receiver. */ static MOZ_MUST_USE bool EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve_, HandleValue thenable_, HandleValue thenVal) { // Need to re-root these to enable wrapping them below. RootedValue promiseToResolve(cx, promiseToResolve_); RootedValue thenable(cx, thenable_); // We enter the `then` callable's compartment so that the job function is // created in that compartment. // That guarantees that the embedding ends up with the right entry global. // This is relevant for some html APIs like fetch that derive information // from said global. RootedObject then(cx, CheckedUnwrap(&thenVal.toObject())); AutoRealm ar(cx, then); // Wrap the `promiseToResolve` and `thenable` arguments. if (!cx->compartment()->wrap(cx, &promiseToResolve)) return false; MOZ_ASSERT(thenable.isObject()); if (!cx->compartment()->wrap(cx, &thenable)) return false; HandlePropertyName funName = cx->names().empty; RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!job) return false; // Store the `then` function on the callback. job->setExtendedSlot(ThenableJobSlot_Handler, ObjectValue(*then)); // Create a dense array to hold the data needed for the reaction job to // work. // The layout is described in the ThenableJobDataIndices enum. RootedArrayObject data(cx, NewDenseFullyAllocatedArray(cx, ThenableJobDataLength)); if (!data) return false; // Set the `promiseToResolve` and `thenable` arguments. data->setDenseInitializedLength(ThenableJobDataLength); data->initDenseElement(ThenableJobDataIndex_Promise, promiseToResolve); data->initDenseElement(ThenableJobDataIndex_Thenable, thenable); // Store the data array on the reaction job. job->setExtendedSlot(ThenableJobSlot_JobData, ObjectValue(*data)); // At this point the promise is guaranteed to be wrapped into the job's // compartment. RootedObject promise(cx, &promiseToResolve.toObject()); Rooted incumbentGlobal(cx, cx->runtime()->getIncumbentGlobal(cx)); return cx->runtime()->enqueuePromiseJob(cx, job, promise, incumbentGlobal); } /** * Tells the embedding to enqueue a Promise resolve thenable built-in job, * based on two parameters: * promiseToResolve - The promise to resolve, obviously. * thenable - The thenable to resolve the Promise with. */ static MOZ_MUST_USE bool EnqueuePromiseResolveThenableBuiltinJob(JSContext* cx, HandleObject promiseToResolve, HandleObject thenable) { cx->check(promiseToResolve, thenable); MOZ_ASSERT(promiseToResolve->is()); MOZ_ASSERT(thenable->is()); HandlePropertyName funName = cx->names().empty; RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveBuiltinThenableJob, 0, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!job) return false; // Store the promise and the thenable on the reaction job. job->setExtendedSlot(BuiltinThenableJobSlot_Promise, ObjectValue(*promiseToResolve)); job->setExtendedSlot(BuiltinThenableJobSlot_Thenable, ObjectValue(*thenable)); Rooted incumbentGlobal(cx, cx->runtime()->getIncumbentGlobal(cx)); return cx->runtime()->enqueuePromiseJob(cx, job, promiseToResolve, incumbentGlobal); } static MOZ_MUST_USE bool AddDummyPromiseReactionForDebugger(JSContext* cx, Handle promise, HandleObject dependentPromise); static MOZ_MUST_USE bool AddPromiseReaction(JSContext* cx, Handle promise, Handle reaction); static JSFunction* GetResolveFunctionFromReject(JSFunction* reject) { MOZ_ASSERT(reject->maybeNative() == RejectPromiseFunction); Value resolveFunVal = reject->getExtendedSlot(RejectFunctionSlot_ResolveFunction); MOZ_ASSERT(IsNativeFunction(resolveFunVal, ResolvePromiseFunction)); return &resolveFunVal.toObject().as(); } static JSFunction* GetRejectFunctionFromResolve(JSFunction* resolve) { MOZ_ASSERT(resolve->maybeNative() == ResolvePromiseFunction); Value rejectFunVal = resolve->getExtendedSlot(ResolveFunctionSlot_RejectFunction); MOZ_ASSERT(IsNativeFunction(rejectFunVal, RejectPromiseFunction)); return &rejectFunVal.toObject().as(); } static JSFunction* GetResolveFunctionFromPromise(PromiseObject* promise) { Value rejectFunVal = promise->getFixedSlot(PromiseSlot_RejectFunction); if (rejectFunVal.isUndefined()) return nullptr; JSObject* rejectFunObj = &rejectFunVal.toObject(); // We can safely unwrap it because all we want is to get the resolve // function. if (IsWrapper(rejectFunObj)) rejectFunObj = UncheckedUnwrap(rejectFunObj); if (!rejectFunObj->is()) return nullptr; JSFunction* rejectFun = &rejectFunObj->as(); // Only the original RejectPromiseFunction has a reference to the resolve // function. if (rejectFun->maybeNative() != &RejectPromiseFunction) return nullptr; // The reject function was already called and cleared its resolve-function // extended slot. if (rejectFun->getExtendedSlot(RejectFunctionSlot_ResolveFunction).isUndefined()) return nullptr; return GetResolveFunctionFromReject(rejectFun); } static void ClearResolutionFunctionSlots(JSFunction* resolutionFun) { JSFunction* resolve; JSFunction* reject; if (resolutionFun->maybeNative() == ResolvePromiseFunction) { resolve = resolutionFun; reject = GetRejectFunctionFromResolve(resolutionFun); } else { resolve = GetResolveFunctionFromReject(resolutionFun); reject = resolutionFun; } resolve->setExtendedSlot(ResolveFunctionSlot_Promise, UndefinedValue()); resolve->setExtendedSlot(ResolveFunctionSlot_RejectFunction, UndefinedValue()); reject->setExtendedSlot(RejectFunctionSlot_Promise, UndefinedValue()); reject->setExtendedSlot(RejectFunctionSlot_ResolveFunction, UndefinedValue()); } // ES2016, 25.4.3.1. steps 3-7. static MOZ_MUST_USE MOZ_ALWAYS_INLINE PromiseObject* CreatePromiseObjectInternal(JSContext* cx, HandleObject proto /* = nullptr */, bool protoIsWrapped /* = false */, bool informDebugger /* = true */) { // Step 3. // Enter the unwrapped proto's compartment, if that's different from // the current one. // All state stored in a Promise's fixed slots must be created in the // same compartment, so we get all of that out of the way here. // (Except for the resolution functions, which are created below.) mozilla::Maybe ar; if (protoIsWrapped) ar.emplace(cx, proto); PromiseObject* promise = NewObjectWithClassProto(cx, proto); if (!promise) return nullptr; // Step 4. promise->initFixedSlot(PromiseSlot_Flags, Int32Value(0)); // Steps 5-6. // Omitted, we allocate our single list of reaction records lazily. // Step 7. // Implicit, the handled flag is unset by default. mozilla::Maybe maybeNow = MaybeNow(); if (MOZ_LIKELY(!ShouldCaptureDebugInfo(cx))) return promise; mozilla::recordreplay::AutoDisallowThreadEvents disallow; // Store an allocation stack so we can later figure out what the // control flow was for some unexpected results. Frightfully expensive, // but oh well. Rooted promiseRoot(cx, promise); PromiseDebugInfo* debugInfo = PromiseDebugInfo::create(cx, promiseRoot, maybeNow); if (!debugInfo) return nullptr; // Let the Debugger know about this Promise. if (informDebugger) Debugger::onNewPromise(cx, promiseRoot); return promiseRoot; } // ES2016, 25.4.3.1. static bool PromiseConstructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Step 1. if (!ThrowIfNotConstructing(cx, args, "Promise")) return false; // Step 2. HandleValue executorVal = args.get(0); if (!IsCallable(executorVal)) return ReportIsNotFunction(cx, executorVal); RootedObject executor(cx, &executorVal.toObject()); // Steps 3-10. RootedObject newTarget(cx, &args.newTarget().toObject()); // If the constructor is called via an Xray wrapper, then the newTarget // hasn't been unwrapped. We want that because, while the actual instance // should be created in the target compartment, the constructor's code // should run in the wrapper's compartment. // // This is so that the resolve and reject callbacks get created in the // wrapper's compartment, which is required for code in that compartment // to freely interact with it, and, e.g., pass objects as arguments, which // it wouldn't be able to if the callbacks were themselves wrapped in Xray // wrappers. // // At the same time, just creating the Promise itself in the wrapper's // compartment wouldn't be helpful: if the wrapper forbids interactions // with objects except for specific actions, such as calling them, then // the code we want to expose it to can't actually treat it as a Promise: // calling .then on it would throw, for example. // // Another scenario where it's important to create the Promise in a // different compartment from the resolution functions is when we want to // give non-privileged code a Promise resolved with the result of a // Promise from privileged code; as a return value of a JS-implemented // API, say. If the resolution functions were unprivileged, then resolving // with a privileged Promise would cause `resolve` to attempt accessing // .then on the passed Promise, which would throw an exception, so we'd // just end up with a rejected Promise. Really, we want to chain the two // Promises, with the unprivileged one resolved with the resolution of the // privileged one. bool needsWrapping = false; RootedObject proto(cx); if (IsWrapper(newTarget)) { JSObject* unwrappedNewTarget = CheckedUnwrap(newTarget); MOZ_ASSERT(unwrappedNewTarget); MOZ_ASSERT(unwrappedNewTarget != newTarget); newTarget = unwrappedNewTarget; { AutoRealm ar(cx, newTarget); Handle global = cx->global(); JSFunction* promiseCtor = GlobalObject::getOrCreatePromiseConstructor(cx, global); if (!promiseCtor) return false; // Promise subclasses don't get the special Xray treatment, so // we only need to do the complex wrapping and unwrapping scheme // described above for instances of Promise itself. if (newTarget == promiseCtor) { needsWrapping = true; proto = GlobalObject::getOrCreatePromisePrototype(cx, cx->global()); if (!proto) return false; } } } if (needsWrapping) { if (!cx->compartment()->wrap(cx, &proto)) return false; } else { if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) return false; } PromiseObject* promise = PromiseObject::create(cx, executor, proto, needsWrapping); if (!promise) return false; // Step 11. args.rval().setObject(*promise); if (needsWrapping) return cx->compartment()->wrap(cx, args.rval()); return true; } // ES2016, 25.4.3.1. steps 3-11. /* static */ PromiseObject* PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /* = nullptr */, bool needsWrapping /* = false */) { MOZ_ASSERT(executor->isCallable()); RootedObject usedProto(cx, proto); // If the proto is wrapped, that means the current function is running // with a different compartment active from the one the Promise instance // is to be created in. // See the comment in PromiseConstructor for details. if (needsWrapping) { MOZ_ASSERT(proto); usedProto = CheckedUnwrap(proto); if (!usedProto) { ReportAccessDenied(cx); return nullptr; } } // Steps 3-7. Rooted promise(cx, CreatePromiseObjectInternal(cx, usedProto, needsWrapping, false)); if (!promise) return nullptr; RootedObject promiseObj(cx, promise); if (needsWrapping && !cx->compartment()->wrap(cx, &promiseObj)) return nullptr; // Step 8. // The resolving functions are created in the compartment active when the // (maybe wrapped) Promise constructor was called. They contain checks and // can unwrap the Promise if required. RootedObject resolveFn(cx); RootedObject rejectFn(cx); if (!CreateResolvingFunctions(cx, promiseObj, &resolveFn, &rejectFn)) return nullptr; // Need to wrap the resolution functions before storing them on the Promise. MOZ_ASSERT(promise->getFixedSlot(PromiseSlot_RejectFunction).isUndefined(), "Slot must be undefined so initFixedSlot can be used"); if (needsWrapping) { AutoRealm ar(cx, promise); RootedObject wrappedRejectFn(cx, rejectFn); if (!cx->compartment()->wrap(cx, &wrappedRejectFn)) return nullptr; promise->initFixedSlot(PromiseSlot_RejectFunction, ObjectValue(*wrappedRejectFn)); } else { promise->initFixedSlot(PromiseSlot_RejectFunction, ObjectValue(*rejectFn)); } // Step 9. bool success; { FixedInvokeArgs<2> args(cx); args[0].setObject(*resolveFn); args[1].setObject(*rejectFn); RootedValue calleeOrRval(cx, ObjectValue(*executor)); success = Call(cx, calleeOrRval, UndefinedHandleValue, args, &calleeOrRval); } // Step 10. if (!success) { RootedValue exceptionVal(cx); if (!MaybeGetAndClearException(cx, &exceptionVal)) return nullptr; RootedValue calleeOrRval(cx, ObjectValue(*rejectFn)); if (!Call(cx, calleeOrRval, UndefinedHandleValue, exceptionVal, &calleeOrRval)) return nullptr; } // Let the Debugger know about this Promise. Debugger::onNewPromise(cx, promise); // Step 11. return promise; } // ES2016, 25.4.3.1. skipping creation of resolution functions and executor // function invocation. /* static */ PromiseObject* PromiseObject::createSkippingExecutor(JSContext* cx) { return CreatePromiseObjectWithoutResolutionFunctions(cx); } class MOZ_STACK_CLASS PromiseForOfIterator : public JS::ForOfIterator { public: using JS::ForOfIterator::ForOfIterator; bool isOptimizedDenseArrayIteration() { MOZ_ASSERT(valueIsIterable()); return index != NOT_ARRAY && IsPackedArray(iterator); } }; static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C, Handle resultCapability, bool* done); // ES2016, 25.4.4.1. static bool Promise_static_all(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); HandleValue iterable = args.get(0); // Step 2 (reordered). HandleValue CVal = args.thisv(); if (!CVal.isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.all call"); return false; } // Step 1. RootedObject C(cx, &CVal.toObject()); // Step 3. Rooted promiseCapability(cx); if (!NewPromiseCapability(cx, C, &promiseCapability, false)) return false; // Steps 4-5. PromiseForOfIterator iter(cx); if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) return AbruptRejectPromise(cx, args, promiseCapability); if (!iter.valueIsIterable()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, "Argument of Promise.all"); return AbruptRejectPromise(cx, args, promiseCapability); } // Step 6 (implicit). // Step 7. bool done; bool result = PerformPromiseAll(cx, iter, C, promiseCapability, &done); // Step 8. if (!result) { // Step 8.a. if (!done) iter.closeThrow(); // Step 8.b. return AbruptRejectPromise(cx, args, promiseCapability); } // Step 9. args.rval().setObject(*promiseCapability.promise()); return true; } static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled_, HandleValue onRejected_, Handle resultCapability); static MOZ_MUST_USE bool PerformPromiseThenWithoutSettleHandlers(JSContext* cx, Handle promise, Handle promiseToResolve, Handle resultCapability); static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); // Unforgeable version of ES2016, 25.4.4.1. MOZ_MUST_USE JSObject* js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) { #ifdef DEBUG for (size_t i = 0, len = promises.length(); i < len; i++) { JSObject* obj = promises[i]; cx->check(obj); MOZ_ASSERT(UncheckedUnwrap(obj)->is()); } #endif // Step 1. RootedObject C(cx, GlobalObject::getOrCreatePromiseConstructor(cx, cx->global())); if (!C) return nullptr; // Step 2 (omitted). // Step 3. Rooted resultCapability(cx); if (!NewPromiseCapability(cx, C, &resultCapability, false)) return nullptr; // Steps 4-6 (omitted). // Step 7. // Implemented as an inlined, simplied version of ES2016 25.4.4.1.1, PerformPromiseAll. { uint32_t promiseCount = promises.length(); // Sub-steps 1-2 (omitted). // Sub-step 3. RootedNativeObject valuesArray(cx, NewDenseFullyAllocatedArray(cx, promiseCount)); if (!valuesArray) return nullptr; valuesArray->ensureDenseInitializedLength(cx, 0, promiseCount); // Sub-step 4. // Create our data holder that holds all the things shared across // every step of the iterator. In particular, this holds the // remainingElementsCount (as an integer reserved slot), the array of // values, and the resolve function from our PromiseCapability. RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray)); Rooted dataHolder(cx); dataHolder = NewPromiseAllDataHolder(cx, resultCapability.promise(), valuesArrayVal, resultCapability.resolve()); if (!dataHolder) return nullptr; // Call PerformPromiseThen with resolve and reject set to nullptr. Rooted resultCapabilityWithoutResolving(cx); resultCapabilityWithoutResolving.promise().set(resultCapability.promise()); // Sub-step 5 (inline in loop-header below). // Sub-step 6. for (uint32_t index = 0; index < promiseCount; index++) { // Steps a-c (omitted). // Step d (implemented after the loop). // Steps e-g (omitted). // Step h. valuesArray->setDenseElement(index, UndefinedHandleValue); // Step i, vastly simplified. RootedObject nextPromiseObj(cx, promises[index]); // Step j. RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllResolveElementFunction, 1, nullptr, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!resolveFunc) return nullptr; // Steps k-o. resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, ObjectValue(*dataHolder)); resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, Int32Value(index)); // Step p. dataHolder->increaseRemainingCount(); // Step q, very roughly. RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc)); RootedValue rejectFunVal(cx, ObjectValue(*resultCapability.reject())); Rooted nextPromise(cx); // GetWaitForAllPromise is used internally only and must not // trigger content-observable effects when registering a reaction. // It's also meant to work on wrapped Promises, potentially from // compartments with principals inaccessible from the current // compartment. To make that work, it unwraps promises with // UncheckedUnwrap, nextPromise = &UncheckedUnwrap(nextPromiseObj)->as(); if (!PerformPromiseThen(cx, nextPromise, resolveFunVal, rejectFunVal, resultCapabilityWithoutResolving)) { return nullptr; } // Step r (inline in loop-header). } // Sub-step d.i (implicit). // Sub-step d.ii. int32_t remainingCount = dataHolder->decreaseRemainingCount(); // Sub-step d.iii-iv. if (remainingCount == 0) { RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray)); if (!ResolvePromiseInternal(cx, resultCapability.promise(), valuesArrayVal)) return nullptr; } } // Step 8 (omitted). // Step 9. return resultCapability.promise(); } static MOZ_MUST_USE bool RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue result, ResolutionMode mode, HandleObject promiseObj) { // The absence of a resolve/reject function can mean that, as an // optimization, those weren't created. In that case, a flag is set on // the Promise object. (It's also possible to not have a resolution // function without that flag being set. This can occur if a Promise // subclass constructor passes null/undefined to `super()`.) // There are also reactions where the Promise itself is missing. For // those, there's nothing left to do here. cx->check(resolutionFun); cx->check(result); cx->check(promiseObj); if (resolutionFun) { RootedValue calleeOrRval(cx, ObjectValue(*resolutionFun)); return Call(cx, calleeOrRval, UndefinedHandleValue, result, &calleeOrRval); } if (!promiseObj) return true; Handle promise = promiseObj.as(); if (promise->state() != JS::PromiseState::Pending) return true; if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) return true; if (mode == ResolveMode) return ResolvePromiseInternal(cx, promise, result); return RejectPromiseInternal(cx, promise, result); } static MOZ_MUST_USE JSObject* CommonStaticResolveRejectImpl(JSContext* cx, HandleValue thisVal, HandleValue argVal, ResolutionMode mode); static bool IsPromiseSpecies(JSContext* cx, JSFunction* species); // ES2019 draft rev dd269df67d37409a6f2099a842b8f5c75ee6fc24 // 25.6.4.1.1 Runtime Semantics: PerformPromiseAll, step 6. // 25.6.4.3.1 Runtime Semantics: PerformPromiseRace, step 3. template static MOZ_MUST_USE bool CommonPerformPromiseAllRace(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C, Handle resultCapability, bool* done, bool resolveReturnsUndefined, T getResolveFun) { RootedObject promiseCtor(cx, GlobalObject::getOrCreatePromiseConstructor(cx, cx->global())); if (!promiseCtor) return false; // Optimized dense array iteration ensures no side-effects take place // during the iteration. bool iterationMayHaveSideEffects = !iterator.isOptimizedDenseArrayIteration(); // Try to optimize when the Promise object is in its default state, seeded // with |C == promiseCtor| because we can only perform this optimization // for the builtin Promise constructor. bool isDefaultPromiseState = C == promiseCtor; bool validatePromiseState = true; PromiseLookup& promiseLookup = cx->realm()->promiseLookup; RootedValue CVal(cx, ObjectValue(*C)); HandleObject resultPromise = resultCapability.promise(); RootedValue resolveFunVal(cx); RootedValue rejectFunVal(cx, ObjectValue(*resultCapability.reject())); // We're reusing rooted variables in the loop below, so we don't need to // declare a gazillion different rooted variables here. Rooted variables // which are reused include "Or" in their name. RootedValue nextValueOrNextPromise(cx); RootedObject nextPromiseObj(cx); RootedValue resolveOrThen(cx); RootedObject thenSpeciesOrBlockedPromise(cx); Rooted thenCapability(cx); while (true) { // Steps a-c, e-g. RootedValue& nextValue = nextValueOrNextPromise; if (!iterator.next(&nextValue, done)) { // Steps b, f. *done = true; // Steps c, g. return false; } // Step d. if (*done) return true; // Set to false when we can skip the [[Get]] for "then" and instead // use the built-in Promise.prototype.then function. bool getThen = true; if (isDefaultPromiseState && validatePromiseState) isDefaultPromiseState = promiseLookup.isDefaultPromiseState(cx); RootedValue& nextPromise = nextValueOrNextPromise; if (isDefaultPromiseState) { PromiseObject* nextValuePromise = nullptr; if (nextValue.isObject() && nextValue.toObject().is()) nextValuePromise = &nextValue.toObject().as(); if (nextValuePromise && promiseLookup.isDefaultInstanceWhenPromiseStateIsSane(cx, nextValuePromise)) { // The below steps don't produce any side-effects, so we can // skip the Promise state revalidation in the next iteration // when the iterator itself also doesn't produce any // side-effects. validatePromiseState = iterationMayHaveSideEffects; // 25.6.4.1.1, step 6.i. // 25.6.4.3.1, step 3.h. // Promise.resolve is a no-op for the default case. MOZ_ASSERT(&nextPromise.toObject() == nextValuePromise); // `nextPromise` uses the built-in `then` function. getThen = false; } else { // Need to revalidate the Promise state in the next iteration, // because CommonStaticResolveRejectImpl may have modified it. validatePromiseState = true; // 25.6.4.1.1, step 6.i. // 25.6.4.3.1, step 3.h. // Inline the call to Promise.resolve. JSObject* res = CommonStaticResolveRejectImpl(cx, CVal, nextValue, ResolveMode); if (!res) return false; nextPromise.setObject(*res); } } else { // 25.6.4.1.1, step 6.i. // 25.6.4.3.1, step 3.h. // Sadly, because someone could have overridden // "resolve" on the canonical Promise constructor. RootedValue& staticResolve = resolveOrThen; if (!GetProperty(cx, C, CVal, cx->names().resolve, &staticResolve)) return false; if (!Call(cx, staticResolve, CVal, nextValue, &nextPromise)) return false; } // Get the resolve function for this iteration. // 25.6.4.1.1, steps 6.j-q. JSObject* resolveFun = getResolveFun(); if (!resolveFun) return false; resolveFunVal.setObject(*resolveFun); // Call |nextPromise.then| with the provided hooks and add // |resultPromise| to the list of dependent promises. // // If |nextPromise.then| is the original |Promise.prototype.then| // function and the call to |nextPromise.then| would use the original // |Promise| constructor to create the resulting promise, we skip the // call to |nextPromise.then| and thus creating a new promise that // would not be observable by content. // 25.6.4.1.1, step 6.r. // 25.6.4.3.1, step 3.i. nextPromiseObj = ToObject(cx, nextPromise); if (!nextPromiseObj) return false; RootedValue& thenVal = resolveOrThen; bool isBuiltinThen; if (getThen) { // We don't use the Promise lookup cache here, because this code // is only called when we had a lookup cache miss, so it's likely // we'd get another cache miss when trying to use the cache here. if (!GetProperty(cx, nextPromiseObj, nextPromise, cx->names().then, &thenVal)) return false; // |nextPromise| is an unwrapped Promise, and |then| is the // original |Promise.prototype.then|, inline it here. isBuiltinThen = nextPromiseObj->is() && IsNativeFunction(thenVal, Promise_then); } else { isBuiltinThen = true; } // By default, the blocked promise is added as an extra entry to the // rejected promises list. bool addToDependent = true; if (isBuiltinThen) { MOZ_ASSERT(nextPromise.isObject()); MOZ_ASSERT(&nextPromise.toObject() == nextPromiseObj); // 25.6.5.4, step 3. RootedObject& thenSpecies = thenSpeciesOrBlockedPromise; if (getThen) { thenSpecies = SpeciesConstructor(cx, nextPromiseObj, JSProto_Promise, IsPromiseSpecies); if (!thenSpecies) return false; } else { thenSpecies = promiseCtor; } // The fast path here and the one in NewPromiseCapability may not // set the resolve and reject handlers, so we need to clear the // fields in case they were set in the previous iteration. thenCapability.resolve().set(nullptr); thenCapability.reject().set(nullptr); // Skip the creation of a built-in Promise object if: // 1. `thenSpecies` is the built-in Promise constructor. // 2. `resolveFun` doesn't return an object, which ensures no // side-effects take place in ResolvePromiseInternal. // 3. The result promise is a built-in Promise object. // 4. The result promise doesn't use the default resolving // functions, which in turn means RunResolutionFunction when // called from PromiseRectionJob won't try to resolve the // promise. if (thenSpecies == promiseCtor && resolveReturnsUndefined && resultPromise->is() && !PromiseHasAnyFlag(resultPromise->as(), PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) { thenCapability.promise().set(resultPromise); addToDependent = false; } else { // 25.6.5.4, step 4. if (!NewPromiseCapability(cx, thenSpecies, &thenCapability, true)) return false; } // 25.6.5.4, step 5. Handle promise = nextPromiseObj.as(); if (!PerformPromiseThen(cx, promise, resolveFunVal, rejectFunVal, thenCapability)) return false; } else { // Optimization failed, do the normal call. RootedValue& ignored = thenVal; if (!Call(cx, thenVal, nextPromise, resolveFunVal, rejectFunVal, &ignored)) return false; // In case the value to depend on isn't an object at all, there's // nothing more to do here: we can only add reactions to Promise // objects (potentially after unwrapping them), and non-object // values can't be Promise objects. This can happen if Promise.all // is called on an object with a `resolve` method that returns // primitives. if (!nextPromise.isObject()) addToDependent = false; } // Adds |resultPromise| to the list of dependent promises. if (addToDependent) { // The object created by the |promise.then| call or the inlined // version of it above is visible to content (either because // |promise.then| was overridden by content and could leak it, // or because a constructor other than the original value of // |Promise| was used to create it). To have both that object and // |resultPromise| show up as dependent promises in the debugger, // add a dummy reaction to the list of reject reactions that // contains |resultPromise|, but otherwise does nothing. RootedObject& blockedPromise = thenSpeciesOrBlockedPromise; blockedPromise = resultPromise; mozilla::Maybe ar; if (IsProxy(nextPromiseObj)) { nextPromiseObj = CheckedUnwrap(nextPromiseObj); if (!nextPromiseObj) { ReportAccessDenied(cx); return false; } if (JS_IsDeadWrapper(nextPromiseObj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } ar.emplace(cx, nextPromiseObj); if (!cx->compartment()->wrap(cx, &blockedPromise)) return false; } // If either the object to depend on (`nextPromiseObj`) or the // object that gets blocked (`resultPromise`) isn't a, // maybe-wrapped, Promise instance, we ignore it. All this does is // lose some small amount of debug information in scenarios that // are highly unlikely to occur in useful code. if (nextPromiseObj->is() && resultPromise->is()) { Handle promise = nextPromiseObj.as(); if (!AddDummyPromiseReactionForDebugger(cx, promise, blockedPromise)) return false; } } } } // ES2016, 25.4.4.1.1. static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C, Handle resultCapability, bool* done) { *done = false; // Step 1. MOZ_ASSERT(C->isConstructor()); // Step 2 (omitted). // Step 3. // We have to be very careful about which compartments we create things in // here. In particular, we have to maintain the invariant that anything // stored in a reserved slot is same-compartment with the object whose // reserved slot it's in. But we want to create the values array in the // Promise's compartment, because that array can get exposed to // code that has access to the Promise (in particular code from // that compartment), and that should work, even if the Promise // compartment is less-privileged than our caller compartment. // // So the plan is as follows: Create the values array in the promise // compartment. Create the PromiseAllResolveElement function // and the data holder in our current compartment. Store a // cross-compartment wrapper to the values array in the holder. This // should be OK because the only things we hand the // PromiseAllResolveElement function to are the "then" calls we do and in // the case when the Promise's compartment is not the current compartment // those are happening over Xrays anyway, which means they get the // canonical "then" function and content can't see our // PromiseAllResolveElement. RootedArrayObject valuesArray(cx); RootedValue valuesArrayVal(cx); if (IsWrapper(resultCapability.promise())) { JSObject* unwrappedPromiseObj = CheckedUnwrap(resultCapability.promise()); MOZ_ASSERT(unwrappedPromiseObj); { AutoRealm ar(cx, unwrappedPromiseObj); valuesArray = NewDenseEmptyArray(cx); if (!valuesArray) return false; } valuesArrayVal.setObject(*valuesArray); if (!cx->compartment()->wrap(cx, &valuesArrayVal)) return false; } else { valuesArray = NewDenseEmptyArray(cx); if (!valuesArray) return false; valuesArrayVal.setObject(*valuesArray); } // Step 4. // Create our data holder that holds all the things shared across // every step of the iterator. In particular, this holds the // remainingElementsCount (as an integer reserved slot), the array of // values, and the resolve function from our PromiseCapability. Rooted dataHolder(cx); dataHolder = NewPromiseAllDataHolder(cx, resultCapability.promise(), valuesArrayVal, resultCapability.resolve()); if (!dataHolder) return false; // Step 5. uint32_t index = 0; auto getResolve = [cx, &valuesArray, &dataHolder, &index]() -> JSObject* { // Step 6.h. { // Scope for the AutoRealm we need to work with valuesArray. We // mostly do this for performance; we could go ahead and do the define via // a cross-compartment proxy instead... AutoRealm ar(cx, valuesArray); if (!NewbornArrayPush(cx, valuesArray, UndefinedValue())) return nullptr; } // Steps 6.j-k. JSFunction* resolveFunc = NewNativeFunction(cx, PromiseAllResolveElementFunction, 1, nullptr,gc::AllocKind::FUNCTION_EXTENDED, GenericObject); if (!resolveFunc) return nullptr; // Steps 6.l, 6.n-p. resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, ObjectValue(*dataHolder)); // Step 6.m. resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, Int32Value(index)); // Step 6.q. dataHolder->increaseRemainingCount(); // Step 6.s. index++; MOZ_ASSERT(index > 0); return resolveFunc; }; // Step 6. if (!CommonPerformPromiseAllRace(cx, iterator, C, resultCapability, done, true, getResolve)) return false; // Step 6.d.ii. int32_t remainingCount = dataHolder->decreaseRemainingCount(); // Steps 6.d.iii-iv. if (remainingCount == 0) { return RunResolutionFunction(cx, resultCapability.resolve(), valuesArrayVal, ResolveMode, resultCapability.promise()); } return true; } // ES2016, 25.4.4.1.2. static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); JSFunction* resolve = &args.callee().as(); RootedValue xVal(cx, args.get(0)); // Step 1. const Value& dataVal = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data); // Step 2. // We use the existence of the data holder as a signal for whether the // Promise was already resolved. Upon resolution, it's reset to // `undefined`. if (dataVal.isUndefined()) { args.rval().setUndefined(); return true; } Rooted data(cx, &dataVal.toObject().as()); // Step 3. resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue()); // Step 4. int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex) .toInt32(); // Step 5. RootedValue valuesVal(cx, data->valuesArray()); RootedObject valuesObj(cx, &valuesVal.toObject()); if (IsProxy(valuesObj)) { // See comment for PerformPromiseAll, step 3 for why we unwrap here. valuesObj = UncheckedUnwrap(valuesObj); if (JS_IsDeadWrapper(valuesObj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } AutoRealm ar(cx, valuesObj); if (!cx->compartment()->wrap(cx, &xVal)) return false; } HandleNativeObject values = valuesObj.as(); // Step 6 (moved under step 10). // Step 7 (moved to step 9). // Step 8. // The index is guaranteed to be initialized to `undefined`. MOZ_ASSERT(values->getDenseElement(index).isUndefined()); values->setDenseElement(index, xVal); // Steps 7,9. uint32_t remainingCount = data->decreaseRemainingCount(); // Step 10. if (remainingCount == 0) { // Step 10.a. (Omitted, happened in PerformPromiseAll.) // Step 10.b. // Step 6 (Adapted to work with PromiseAllDataHolder's layout). RootedObject resolveAllFun(cx, data->resolveObj()); RootedObject promiseObj(cx, data->promiseObj()); if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj)) return false; } // Step 11. args.rval().setUndefined(); return true; } static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C, Handle resultCapability, bool* done); // ES2016, 25.4.4.3. static bool Promise_static_race(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); HandleValue iterable = args.get(0); // Step 2 (reordered). HandleValue CVal = args.thisv(); if (!CVal.isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.race call"); return false; } // Step 1. RootedObject C(cx, &CVal.toObject()); // Step 3. Rooted promiseCapability(cx); if (!NewPromiseCapability(cx, C, &promiseCapability, false)) return false; // Steps 4-5. PromiseForOfIterator iter(cx); if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) return AbruptRejectPromise(cx, args, promiseCapability); if (!iter.valueIsIterable()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, "Argument of Promise.race"); return AbruptRejectPromise(cx, args, promiseCapability); } // Step 6 (implicit). // Step 7. bool done; bool result = PerformPromiseRace(cx, iter, C, promiseCapability, &done); // Step 8. if (!result) { // Step 8.a. if (!done) iter.closeThrow(); // Step 8.b. return AbruptRejectPromise(cx, args, promiseCapability); } // Step 9. args.rval().setObject(*promiseCapability.promise()); return true; } // ES2016, 25.4.4.3.1. static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C, Handle resultCapability, bool* done) { *done = false; // Step 1. MOZ_ASSERT(C->isConstructor()); // Step 2 (omitted). // BlockOnPromise fast path requires the passed onFulfilled function // doesn't return an object value, because otherwise the skipped promise // creation is detectable due to missing property lookups. bool isDefaultResolveFn = IsNativeFunction(resultCapability.resolve(), ResolvePromiseFunction); auto getResolve = [&resultCapability]() -> JSObject* { return resultCapability.resolve(); }; // Step 3. return CommonPerformPromiseAllRace(cx, iterator, C, resultCapability, done, isDefaultResolveFn, getResolve); } // ES2016, Sub-steps of 25.4.4.4 and 25.4.4.5. static MOZ_MUST_USE JSObject* CommonStaticResolveRejectImpl(JSContext* cx, HandleValue thisVal, HandleValue argVal, ResolutionMode mode) { // Steps 1-2. if (!thisVal.isObject()) { const char* msg = mode == ResolveMode ? "Receiver of Promise.resolve call" : "Receiver of Promise.reject call"; JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, msg); return nullptr; } RootedObject C(cx, &thisVal.toObject()); // Step 3 of Resolve. if (mode == ResolveMode && argVal.isObject()) { RootedObject xObj(cx, &argVal.toObject()); bool isPromise = false; if (xObj->is()) { isPromise = true; } else if (IsWrapper(xObj)) { // Treat instances of Promise from other compartments as Promises // here, too. // It's important to do the GetProperty for the `constructor` // below through the wrapper, because wrappers can change the // outcome, so instead of unwrapping and then performing the // GetProperty, just check here and then operate on the original // object again. JSObject* unwrappedObject = CheckedUnwrap(xObj); if (unwrappedObject && unwrappedObject->is()) isPromise = true; } if (isPromise) { RootedValue ctorVal(cx); if (!GetProperty(cx, xObj, xObj, cx->names().constructor, &ctorVal)) return nullptr; if (ctorVal == thisVal) return xObj; } } // Step 4 of Resolve, 3 of Reject. Rooted capability(cx); if (!NewPromiseCapability(cx, C, &capability, true)) return nullptr; // Step 5 of Resolve, 4 of Reject. if (!RunResolutionFunction(cx, mode == ResolveMode ? capability.resolve() : capability.reject(), argVal, mode, capability.promise())) { return nullptr; } // Step 6 of Resolve, 4 of Reject. return capability.promise(); } MOZ_MUST_USE JSObject* js::PromiseResolve(JSContext* cx, HandleObject constructor, HandleValue value) { RootedValue C(cx, ObjectValue(*constructor)); return CommonStaticResolveRejectImpl(cx, C, value, ResolveMode); } /** * ES2016, 25.4.4.4, Promise.reject. */ static bool Promise_reject(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); HandleValue thisVal = args.thisv(); HandleValue argVal = args.get(0); JSObject* result = CommonStaticResolveRejectImpl(cx, thisVal, argVal, RejectMode); if (!result) return false; args.rval().setObject(*result); return true; } /** * Unforgeable version of ES2016, 25.4.4.4, Promise.reject. */ /* static */ JSObject* PromiseObject::unforgeableReject(JSContext* cx, HandleValue value) { JSObject* promiseCtor = JS::GetPromiseConstructor(cx); if (!promiseCtor) return nullptr; RootedValue cVal(cx, ObjectValue(*promiseCtor)); return CommonStaticResolveRejectImpl(cx, cVal, value, RejectMode); } /** * ES2016, 25.4.4.5, Promise.resolve. */ static bool Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); HandleValue thisVal = args.thisv(); HandleValue argVal = args.get(0); JSObject* result = CommonStaticResolveRejectImpl(cx, thisVal, argVal, ResolveMode); if (!result) return false; args.rval().setObject(*result); return true; } /** * Unforgeable version of ES2016, 25.4.4.5, Promise.resolve. */ /* static */ JSObject* PromiseObject::unforgeableResolve(JSContext* cx, HandleValue value) { JSObject* promiseCtor = JS::GetPromiseConstructor(cx); if (!promiseCtor) return nullptr; RootedValue cVal(cx, ObjectValue(*promiseCtor)); return CommonStaticResolveRejectImpl(cx, cVal, value, ResolveMode); } /** * ES2016, 25.4.4.6 get Promise [ @@species ] */ static bool Promise_static_species(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Step 1: Return the this value. args.rval().set(args.thisv()); return true; } // ES2016, 25.4.5.1, implemented in Promise.js. enum class IncumbentGlobalObject { // Do not use the incumbent global, this is a special case used by the // debugger. No, // Use incumbent global, this is the normal operation. Yes }; static PromiseReactionRecord* NewReactionRecord(JSContext* cx, Handle resultCapability, HandleValue onFulfilled, HandleValue onRejected, IncumbentGlobalObject incumbentGlobalObjectOption) { #ifdef DEBUG if (resultCapability.promise()) { if (incumbentGlobalObjectOption == IncumbentGlobalObject::Yes) { if (resultCapability.promise()->is()) { // If `resultCapability.promise` is a Promise object, // `resultCapability.{resolve,reject}` may be optimized out, // but if they're not, they should be callable. MOZ_ASSERT_IF(resultCapability.resolve(), IsCallable(resultCapability.resolve())); MOZ_ASSERT_IF(resultCapability.reject(), IsCallable(resultCapability.reject())); } else { // If `resultCapability.promise` is a non-Promise object // (including wrapped Promise object), // `resultCapability.{resolve,reject}` should be callable. MOZ_ASSERT(resultCapability.resolve()); MOZ_ASSERT(IsCallable(resultCapability.resolve())); MOZ_ASSERT(resultCapability.reject()); MOZ_ASSERT(IsCallable(resultCapability.reject())); } } else { // For debugger usage, `resultCapability.promise` should be a // maybe-wrapped Promise object. The other fields are not used. // // This is the only case where we allow `resolve` and `reject` to // be null when the `promise` field is not a PromiseObject. JSObject* unwrappedPromise = UncheckedUnwrap(resultCapability.promise()); MOZ_ASSERT(unwrappedPromise->is()); MOZ_ASSERT(!resultCapability.resolve()); MOZ_ASSERT(!resultCapability.reject()); } } else { // `resultCapability.promise` is null for the following cases: // * resulting Promise is known to be unused // * Async Generator // In any case, other fields are also not used. MOZ_ASSERT(!resultCapability.resolve()); MOZ_ASSERT(!resultCapability.reject()); MOZ_ASSERT(incumbentGlobalObjectOption == IncumbentGlobalObject::Yes); } #endif // Ensure the onFulfilled handler has the expected type. MOZ_ASSERT(onFulfilled.isInt32() || onFulfilled.isObjectOrNull()); MOZ_ASSERT_IF(onFulfilled.isObject(), IsCallable(onFulfilled)); MOZ_ASSERT_IF(onFulfilled.isInt32(), 0 <= onFulfilled.toInt32() && onFulfilled.toInt32() < PromiseHandlerLimit); // Ensure the onRejected handler has the expected type. MOZ_ASSERT(onRejected.isInt32() || onRejected.isObjectOrNull()); MOZ_ASSERT_IF(onRejected.isObject(), IsCallable(onRejected)); MOZ_ASSERT_IF(onRejected.isInt32(), 0 <= onRejected.toInt32() && onRejected.toInt32() < PromiseHandlerLimit); // Handlers must either both be present or both be absent. MOZ_ASSERT(onFulfilled.isNull() == onRejected.isNull()); RootedObject incumbentGlobalObject(cx); if (incumbentGlobalObjectOption == IncumbentGlobalObject::Yes) { if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobalObject)) return nullptr; } PromiseReactionRecord* reaction = NewBuiltinClassInstance(cx); if (!reaction) return nullptr; cx->check(resultCapability.promise()); cx->check(onFulfilled); cx->check(onRejected); cx->check(resultCapability.resolve()); cx->check(resultCapability.reject()); cx->check(incumbentGlobalObject); reaction->setFixedSlot(ReactionRecordSlot_Promise, ObjectOrNullValue(resultCapability.promise())); reaction->setFixedSlot(ReactionRecordSlot_Flags, Int32Value(0)); reaction->setFixedSlot(ReactionRecordSlot_OnFulfilled, onFulfilled); reaction->setFixedSlot(ReactionRecordSlot_OnRejected, onRejected); reaction->setFixedSlot(ReactionRecordSlot_Resolve, ObjectOrNullValue(resultCapability.resolve())); reaction->setFixedSlot(ReactionRecordSlot_Reject, ObjectOrNullValue(resultCapability.reject())); reaction->setFixedSlot(ReactionRecordSlot_IncumbentGlobalObject, ObjectOrNullValue(incumbentGlobalObject)); return reaction; } static bool IsPromiseSpecies(JSContext* cx, JSFunction* species) { return species->maybeNative() == Promise_static_species; } static bool PromiseThenNewPromiseCapability(JSContext* cx, HandleObject promiseObj, CreateDependentPromise createDependent, MutableHandle resultCapability) { if (createDependent != CreateDependentPromise::Never) { // Step 3. RootedObject C(cx, SpeciesConstructor(cx, promiseObj, JSProto_Promise, IsPromiseSpecies)); if (!C) return false; if (createDependent == CreateDependentPromise::Always || !IsNativeFunction(C, PromiseConstructor)) { // Step 4. if (!NewPromiseCapability(cx, C, resultCapability, true)) return false; } } return true; } // ES2016, 25.4.5.3., steps 3-5. MOZ_MUST_USE bool js::OriginalPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled, HandleValue onRejected, MutableHandleObject dependent, CreateDependentPromise createDependent) { RootedObject promiseObj(cx, promise); if (promise->compartment() != cx->compartment()) { if (!cx->compartment()->wrap(cx, &promiseObj)) return false; } // Steps 3-4. Rooted resultCapability(cx); if (!PromiseThenNewPromiseCapability(cx, promiseObj, createDependent, &resultCapability)) return false; // Step 5. if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultCapability)) return false; dependent.set(resultCapability.promise()); return true; } static MOZ_MUST_USE bool OriginalPromiseThenWithoutSettleHandlers(JSContext* cx, Handle promise, Handle promiseToResolve) { cx->check(promise); // Steps 3-4. Rooted resultCapability(cx); if (!PromiseThenNewPromiseCapability(cx, promise, CreateDependentPromise::SkipIfCtorUnobservable, &resultCapability)) { return false; } // Step 5. return PerformPromiseThenWithoutSettleHandlers(cx, promise, promiseToResolve, resultCapability); } static bool CanCallOriginalPromiseThenBuiltin(JSContext* cx, HandleValue promise) { return promise.isObject() && promise.toObject().is() && cx->realm()->promiseLookup.isDefaultInstance(cx, &promise.toObject().as()); } // ES2016, 25.4.5.3., steps 3-5. static bool OriginalPromiseThenBuiltin(JSContext* cx, HandleValue promiseVal, HandleValue onFulfilled, HandleValue onRejected, MutableHandleValue rval, bool rvalUsed) { cx->check(promiseVal, onFulfilled, onRejected); MOZ_ASSERT(CanCallOriginalPromiseThenBuiltin(cx, promiseVal)); Rooted promise(cx, &promiseVal.toObject().as()); // Steps 3-4. Rooted resultCapability(cx); if (rvalUsed) { PromiseObject* resultPromise = CreatePromiseObjectWithoutResolutionFunctions(cx); if (!resultPromise) return false; resultCapability.promise().set(resultPromise); } // Step 5. if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultCapability)) return false; if (rvalUsed) rval.setObject(*resultCapability.promise()); else rval.setUndefined(); return true; } static MOZ_MUST_USE bool PerformPromiseThenWithReaction(JSContext* cx, Handle promise, Handle reaction); // Some async/await functions are implemented here instead of // js/src/builtin/AsyncFunction.cpp, to call Promise internal functions. // ES 2018 draft 14.6.11 and 14.7.14 step 1. MOZ_MUST_USE PromiseObject* js::CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal) { // Step 1. PromiseObject* promise = CreatePromiseObjectWithoutResolutionFunctions(cx); if (!promise) return nullptr; AddPromiseFlags(*promise, PROMISE_FLAG_ASYNC); promise->setFixedSlot(PromiseSlot_AwaitGenerator, generatorVal); return promise; } bool js::IsPromiseForAsync(JSObject* promise) { return promise->is() && PromiseHasAnyFlag(promise->as(), PROMISE_FLAG_ASYNC); } // ES 2018 draft 25.5.5.2 steps 3.f, 3.g. MOZ_MUST_USE bool js::AsyncFunctionThrown(JSContext* cx, Handle resultPromise) { // Step 3.f. RootedValue exc(cx); if (!MaybeGetAndClearException(cx, &exc)) return false; if (!RejectPromiseInternal(cx, resultPromise, exc)) return false; // Step 3.g. return true; } // ES 2018 draft 25.5.5.2 steps 3.d-e, 3.g. MOZ_MUST_USE bool js::AsyncFunctionReturned(JSContext* cx, Handle resultPromise, HandleValue value) { // Steps 3.d-e. if (!ResolvePromiseInternal(cx, resultPromise, value)) return false; // Step 3.g. return true; } // Helper function that performs the equivalent steps as // Async Iteration proposal 4.1 Await steps 2-3, 6-9 or similar. template static MOZ_MUST_USE bool InternalAwait(JSContext* cx, HandleValue value, HandleObject resultPromise, HandleValue onFulfilled, HandleValue onRejected, T extraStep) { MOZ_ASSERT(onFulfilled.isInt32()); MOZ_ASSERT(onRejected.isInt32()); // Step 2. Rooted promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); if (!promise) return false; // Step 3. if (!ResolvePromiseInternal(cx, promise, value)) return false; // Step 7-8. Rooted resultCapability(cx); resultCapability.promise().set(resultPromise); Rooted reaction(cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected, IncumbentGlobalObject::Yes)); if (!reaction) return false; // Step 6. extraStep(reaction); // Step 9. return PerformPromiseThenWithReaction(cx, promise, reaction); } // ES 2018 draft 25.5.5.3 steps 2-10. MOZ_MUST_USE bool js::AsyncFunctionAwait(JSContext* cx, Handle resultPromise, HandleValue value) { // Steps 4-5. RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncFunctionAwaitedFulfilled)); RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncFunctionAwaitedRejected)); // Steps 2-3, 6-10. auto extra = [](Handle reaction) { reaction->setIsAsyncFunction(); }; return InternalAwait(cx, value, resultPromise, onFulfilled, onRejected, extra); } // Async Iteration proposal 4.1 Await steps 2-9. MOZ_MUST_USE bool js::AsyncGeneratorAwait(JSContext* cx, Handle asyncGenObj, HandleValue value) { // Steps 4-5. RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorAwaitedFulfilled)); RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorAwaitedRejected)); // Steps 2-3, 6-9. auto extra = [&](Handle reaction) { reaction->setIsAsyncGenerator(asyncGenObj); }; return InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra); } // Async Iteration proposal 11.1.3.2.1 %AsyncFromSyncIteratorPrototype%.next // Async Iteration proposal 11.1.3.2.2 %AsyncFromSyncIteratorPrototype%.return // Async Iteration proposal 11.1.3.2.3 %AsyncFromSyncIteratorPrototype%.throw bool js::AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind completionKind) { // Step 1. HandleValue thisVal = args.thisv(); // Step 2. Rooted resultPromise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); if (!resultPromise) return false; // Step 3. if (!thisVal.isObject() || !thisVal.toObject().is()) { // NB: See https://github.com/tc39/proposal-async-iteration/issues/105 // for why this check shouldn't be necessary as long as we can ensure // the Async-from-Sync iterator can't be accessed directly by user // code. // Step 3.a. RootedValue badGeneratorError(cx); if (!GetTypeError(cx, JSMSG_NOT_AN_ASYNC_ITERATOR, &badGeneratorError)) return false; // Step 3.b. if (!RejectPromiseInternal(cx, resultPromise, badGeneratorError)) return false; // Step 3.c. args.rval().setObject(*resultPromise); return true; } Rooted asyncIter( cx, &thisVal.toObject().as()); // Step 4. RootedObject iter(cx, asyncIter->iterator()); RootedValue func(cx); if (completionKind == CompletionKind::Normal) { // 11.1.3.2.1 steps 5-6 (partially). func.set(asyncIter->nextMethod()); } else if (completionKind == CompletionKind::Return) { // 11.1.3.2.2 steps 5-6. if (!GetProperty(cx, iter, iter, cx->names().return_, &func)) return AbruptRejectPromise(cx, args, resultPromise, nullptr); // Step 7. if (func.isNullOrUndefined()) { // Step 7.a. JSObject* resultObj = CreateIterResultObject(cx, args.get(0), true); if (!resultObj) return AbruptRejectPromise(cx, args, resultPromise, nullptr); RootedValue resultVal(cx, ObjectValue(*resultObj)); // Step 7.b. if (!ResolvePromiseInternal(cx, resultPromise, resultVal)) return AbruptRejectPromise(cx, args, resultPromise, nullptr); // Step 7.c. args.rval().setObject(*resultPromise); return true; } } else { // 11.1.3.2.3 steps 5-6. MOZ_ASSERT(completionKind == CompletionKind::Throw); if (!GetProperty(cx, iter, iter, cx->names().throw_, &func)) return AbruptRejectPromise(cx, args, resultPromise, nullptr); // Step 7. if (func.isNullOrUndefined()) { // Step 7.a. if (!RejectPromiseInternal(cx, resultPromise, args.get(0))) return AbruptRejectPromise(cx, args, resultPromise, nullptr); // Step 7.b. args.rval().setObject(*resultPromise); return true; } } // 11.1.3.2.1 steps 5-6 (partially). // 11.1.3.2.2, 11.1.3.2.3 steps 8-9. RootedValue iterVal(cx, ObjectValue(*iter)); RootedValue resultVal(cx); if (!Call(cx, func, iterVal, args.get(0), &resultVal)) return AbruptRejectPromise(cx, args, resultPromise, nullptr); // 11.1.3.2.1 steps 5-6 (partially). // 11.1.3.2.2, 11.1.3.2.3 steps 10. if (!resultVal.isObject()) { CheckIsObjectKind kind; switch (completionKind) { case CompletionKind::Normal: kind = CheckIsObjectKind::IteratorNext; break; case CompletionKind::Throw: kind = CheckIsObjectKind::IteratorThrow; break; case CompletionKind::Return: kind = CheckIsObjectKind::IteratorReturn; break; } MOZ_ALWAYS_FALSE(ThrowCheckIsObject(cx, kind)); return AbruptRejectPromise(cx, args, resultPromise, nullptr); } RootedObject resultObj(cx, &resultVal.toObject()); // Following step numbers are for 11.1.3.2.1. // For 11.1.3.2.2 and 11.1.3.2.3, steps 7-16 corresponds to steps 11-20. // Steps 7-8. RootedValue doneVal(cx); if (!GetProperty(cx, resultObj, resultObj, cx->names().done, &doneVal)) return AbruptRejectPromise(cx, args, resultPromise, nullptr); bool done = ToBoolean(doneVal); // Steps 9-10. RootedValue value(cx); if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value)) return AbruptRejectPromise(cx, args, resultPromise, nullptr); // Steps 13-14. RootedValue onFulfilled(cx, Int32Value(done ? PromiseHandlerAsyncFromSyncIteratorValueUnwrapDone : PromiseHandlerAsyncFromSyncIteratorValueUnwrapNotDone)); RootedValue onRejected(cx, Int32Value(PromiseHandlerThrower)); // Steps 11-12, 15. auto extra = [](Handle reaction) { }; if (!InternalAwait(cx, value, resultPromise, onFulfilled, onRejected, extra)) return false; // Step 16. args.rval().setObject(*resultPromise); return true; } enum class ResumeNextKind { Enqueue, Reject, Resolve }; static MOZ_MUST_USE bool AsyncGeneratorResumeNext(JSContext* cx, Handle asyncGenObj, ResumeNextKind kind, HandleValue valueOrException = UndefinedHandleValue, bool done = false); // Async Iteration proposal 11.4.3.3. MOZ_MUST_USE bool js::AsyncGeneratorResolve(JSContext* cx, Handle asyncGenObj, HandleValue value, bool done) { return AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Resolve, value, done); } // Async Iteration proposal 11.4.3.4. MOZ_MUST_USE bool js::AsyncGeneratorReject(JSContext* cx, Handle asyncGenObj, HandleValue exception) { return AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Reject, exception); } // Async Iteration proposal 11.4.3.5. static MOZ_MUST_USE bool AsyncGeneratorResumeNext(JSContext* cx, Handle asyncGenObj, ResumeNextKind kind, HandleValue valueOrException_ /* = UndefinedHandleValue */, bool done /* = false */) { RootedValue valueOrException(cx, valueOrException_); while (true) { switch (kind) { case ResumeNextKind::Enqueue: // No further action required. break; case ResumeNextKind::Reject: { // 11.4.3.4 AsyncGeneratorReject ( generator, exception ) HandleValue exception = valueOrException; // Step 1 (implicit). // Steps 2-3. MOZ_ASSERT(!asyncGenObj->isQueueEmpty()); // Step 4. AsyncGeneratorRequest* request = AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj); if (!request) return false; // Step 5. Rooted resultPromise(cx, request->promise()); asyncGenObj->cacheRequest(request); // Step 6. if (!RejectPromiseInternal(cx, resultPromise, exception)) return false; // Steps 7-8. break; } case ResumeNextKind::Resolve: { // 11.4.3.3 AsyncGeneratorResolve ( generator, value, done ) HandleValue value = valueOrException; // Step 1 (implicit). // Steps 2-3. MOZ_ASSERT(!asyncGenObj->isQueueEmpty()); // Step 4. AsyncGeneratorRequest* request = AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj); if (!request) return false; // Step 5. Rooted resultPromise(cx, request->promise()); asyncGenObj->cacheRequest(request); // Step 6. JSObject* resultObj = CreateIterResultObject(cx, value, done); if (!resultObj) return false; RootedValue resultValue(cx, ObjectValue(*resultObj)); // Step 7. if (!ResolvePromiseInternal(cx, resultPromise, resultValue)) return false; // Steps 8-9. break; } } // Step 1 (implicit). // Steps 2-3. MOZ_ASSERT(!asyncGenObj->isExecuting()); // Step 4. if (asyncGenObj->isAwaitingYieldReturn() || asyncGenObj->isAwaitingReturn()) return true; // Steps 5-6. if (asyncGenObj->isQueueEmpty()) return true; // Steps 7-8. Rooted request( cx, AsyncGeneratorObject::peekRequest(asyncGenObj)); if (!request) return false; // Step 9. CompletionKind completionKind = request->completionKind(); // Step 10. if (completionKind != CompletionKind::Normal) { // Step 10.a. if (asyncGenObj->isSuspendedStart()) asyncGenObj->setCompleted(); // Step 10.b. if (asyncGenObj->isCompleted()) { RootedValue value(cx, request->completionValue()); // Step 10.b.i. if (completionKind == CompletionKind::Return) { // Steps 10.b.i.1. asyncGenObj->setAwaitingReturn(); // Steps 10.b.i.4-6 (reordered). static constexpr int32_t ResumeNextReturnFulfilled = PromiseHandlerAsyncGeneratorResumeNextReturnFulfilled; static constexpr int32_t ResumeNextReturnRejected = PromiseHandlerAsyncGeneratorResumeNextReturnRejected; RootedValue onFulfilled(cx, Int32Value(ResumeNextReturnFulfilled)); RootedValue onRejected(cx, Int32Value(ResumeNextReturnRejected)); // Steps 10.b.i.2-3, 7-10. auto extra = [&](Handle reaction) { reaction->setIsAsyncGenerator(asyncGenObj); }; return InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra); } // Step 10.b.ii.1. MOZ_ASSERT(completionKind == CompletionKind::Throw); // Steps 10.b.ii.2-3. kind = ResumeNextKind::Reject; valueOrException.set(value); // |done| is unused for ResumeNextKind::Reject. continue; } } else if (asyncGenObj->isCompleted()) { // Step 11. kind = ResumeNextKind::Resolve; valueOrException.setUndefined(); done = true; continue; } // Step 12. MOZ_ASSERT(asyncGenObj->isSuspendedStart() || asyncGenObj->isSuspendedYield()); // Step 16 (reordered). asyncGenObj->setExecuting(); RootedValue argument(cx, request->completionValue()); if (completionKind == CompletionKind::Return) { // 11.4.3.7 AsyncGeneratorYield step 8.b-e. // Since we don't have the place that handles return from yield // inside the generator, handle the case here, with extra state // State_AwaitingYieldReturn. asyncGenObj->setAwaitingYieldReturn(); static constexpr int32_t YieldReturnAwaitedFulfilled = PromiseHandlerAsyncGeneratorYieldReturnAwaitedFulfilled; static constexpr int32_t YieldReturnAwaitedRejected = PromiseHandlerAsyncGeneratorYieldReturnAwaitedRejected; RootedValue onFulfilled(cx, Int32Value(YieldReturnAwaitedFulfilled)); RootedValue onRejected(cx, Int32Value(YieldReturnAwaitedRejected)); auto extra = [&](Handle reaction) { reaction->setIsAsyncGenerator(asyncGenObj); }; return InternalAwait(cx, argument, nullptr, onFulfilled, onRejected, extra); } // Steps 13-15, 17-21. return AsyncGeneratorResume(cx, asyncGenObj, completionKind, argument); } } // Async Iteration proposal 11.4.3.6. MOZ_MUST_USE bool js::AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal, CompletionKind completionKind, HandleValue completionValue, MutableHandleValue result) { // Step 1 (implicit). // Step 2. Rooted resultPromise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); if (!resultPromise) return false; // Step 3. if (!asyncGenVal.isObject() || !asyncGenVal.toObject().is()) { // Step 3.a. RootedValue badGeneratorError(cx); if (!GetTypeError(cx, JSMSG_NOT_AN_ASYNC_GENERATOR, &badGeneratorError)) return false; // Step 3.b. if (!RejectPromiseInternal(cx, resultPromise, badGeneratorError)) return false; // Step 3.c. result.setObject(*resultPromise); return true; } Rooted asyncGenObj( cx, &asyncGenVal.toObject().as()); // Step 5 (reordered). Rooted request( cx, AsyncGeneratorObject::createRequest(cx, asyncGenObj, completionKind, completionValue, resultPromise)); if (!request) return false; // Steps 4, 6. if (!AsyncGeneratorObject::enqueueRequest(cx, asyncGenObj, request)) return false; // Step 7. if (!asyncGenObj->isExecuting()) { // Step 8. if (!AsyncGeneratorResumeNext(cx, asyncGenObj, ResumeNextKind::Enqueue)) return false; } // Step 9. result.setObject(*resultPromise); return true; } static bool Promise_catch_impl(JSContext* cx, unsigned argc, Value* vp, bool rvalUsed) { CallArgs args = CallArgsFromVp(argc, vp); HandleValue thisVal = args.thisv(); HandleValue onFulfilled = UndefinedHandleValue; HandleValue onRejected = args.get(0); // Fast path when the default Promise state is intact. if (CanCallOriginalPromiseThenBuiltin(cx, thisVal)) { return OriginalPromiseThenBuiltin(cx, thisVal, onFulfilled, onRejected, args.rval(), rvalUsed); } // Step 1. RootedValue thenVal(cx); if (!GetProperty(cx, thisVal, cx->names().then, &thenVal)) return false; if (IsNativeFunction(thenVal, &Promise_then)) return Promise_then_impl(cx, thisVal, onFulfilled, onRejected, args.rval(), rvalUsed); return Call(cx, thenVal, thisVal, UndefinedHandleValue, onRejected, args.rval()); } static MOZ_ALWAYS_INLINE bool IsPromiseThenOrCatchRetValImplicitlyUsed(JSContext* cx) { // The returned promise of Promise#then and Promise#catch contains // stack info if async stack is enabled. Even if their return value is not // used explicitly in the script, the stack info is observable in devtools // and profilers. We shouldn't apply the optimization not to allocate the // returned Promise object if the it's implicitly used by them. // // FIXME: Once bug 1280819 gets fixed, we can use ShouldCaptureDebugInfo. if (!cx->options().asyncStack()) return false; // If devtools is opened, the current realm will become debuggee. if (cx->realm()->isDebuggee()) return true; // There are 2 profilers, and they can be independently enabled. if (cx->runtime()->geckoProfiler().enabled()) return true; if (JS::IsProfileTimelineRecordingEnabled()) return true; // The stack is also observable from Error#stack, but we don't care since // it's nonstandard feature. return false; } // ES2016, 25.4.5.3. static bool Promise_catch_noRetVal(JSContext* cx, unsigned argc, Value* vp) { return Promise_catch_impl(cx, argc, vp, IsPromiseThenOrCatchRetValImplicitlyUsed(cx)); } // ES2016, 25.4.5.3. static bool Promise_catch(JSContext* cx, unsigned argc, Value* vp) { return Promise_catch_impl(cx, argc, vp, true); } static bool Promise_then_impl(JSContext* cx, HandleValue promiseVal, HandleValue onFulfilled, HandleValue onRejected, MutableHandleValue rval, bool rvalUsed) { // Step 1 (implicit). // Step 2. if (!promiseVal.isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.prototype.then call"); return false; } // Fast path when the default Promise state is intact. if (CanCallOriginalPromiseThenBuiltin(cx, promiseVal)) return OriginalPromiseThenBuiltin(cx, promiseVal, onFulfilled, onRejected, rval, rvalUsed); RootedObject promiseObj(cx, &promiseVal.toObject()); Rooted promise(cx); if (promiseObj->is()) { promise = &promiseObj->as(); } else { JSObject* unwrappedPromiseObj = CheckedUnwrap(promiseObj); if (!unwrappedPromiseObj) { ReportAccessDenied(cx); return false; } if (!unwrappedPromiseObj->is()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, "Promise", "then", "value"); return false; } promise = &unwrappedPromiseObj->as(); } // Steps 3-5. CreateDependentPromise createDependent = rvalUsed ? CreateDependentPromise::Always : CreateDependentPromise::SkipIfCtorUnobservable; RootedObject resultPromise(cx); if (!OriginalPromiseThen(cx, promise, onFulfilled, onRejected, &resultPromise, createDependent)) { return false; } if (rvalUsed) rval.setObject(*resultPromise); else rval.setUndefined(); return true; } // ES2016, 25.4.5.3. bool Promise_then_noRetVal(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return Promise_then_impl(cx, args.thisv(), args.get(0), args.get(1), args.rval(), IsPromiseThenOrCatchRetValImplicitlyUsed(cx)); } // ES2016, 25.4.5.3. static bool Promise_then(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return Promise_then_impl(cx, args.thisv(), args.get(0), args.get(1), args.rval(), true); } // ES2016, 25.4.5.3.1. static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled_, HandleValue onRejected_, Handle resultCapability) { // Step 1 (implicit). // Step 2 (implicit). // Step 3. RootedValue onFulfilled(cx, onFulfilled_); if (!IsCallable(onFulfilled)) onFulfilled = Int32Value(PromiseHandlerIdentity); // Step 4. RootedValue onRejected(cx, onRejected_); if (!IsCallable(onRejected)) onRejected = Int32Value(PromiseHandlerThrower); // Step 7. Rooted reaction(cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected, IncumbentGlobalObject::Yes)); if (!reaction) return false; return PerformPromiseThenWithReaction(cx, promise, reaction); } static MOZ_MUST_USE bool PerformPromiseThenWithoutSettleHandlers(JSContext* cx, Handle promise, Handle promiseToResolve, Handle resultCapability) { // Step 1 (implicit). // Step 2 (implicit). // Step 3. HandleValue onFulfilled = NullHandleValue; // Step 4. HandleValue onRejected = NullHandleValue; // Step 7. Rooted reaction(cx, NewReactionRecord(cx, resultCapability, onFulfilled, onRejected, IncumbentGlobalObject::Yes)); if (!reaction) return false; reaction->setIsDefaultResolvingHandler(promiseToResolve); return PerformPromiseThenWithReaction(cx, promise, reaction); } static MOZ_MUST_USE bool PerformPromiseThenWithReaction(JSContext* cx, Handle promise, Handle reaction) { JS::PromiseState state = promise->state(); int32_t flags = promise->flags(); if (state == JS::PromiseState::Pending) { // Steps 5,6 (reordered). // Instead of creating separate reaction records for fulfillment and // rejection, we create a combined record. All places we use the record // can handle that. if (!AddPromiseReaction(cx, promise, reaction)) return false; } // Steps 8,9. else { // Step 9.a. MOZ_ASSERT_IF(state != JS::PromiseState::Fulfilled, state == JS::PromiseState::Rejected); // Step 8.a. / 9.b. RootedValue valueOrReason(cx, promise->valueOrReason()); // We might be operating on a promise from another compartment. In // that case, we need to wrap the result/reason value before using it. if (!cx->compartment()->wrap(cx, &valueOrReason)) return false; // Step 9.c. if (state == JS::PromiseState::Rejected && !(flags & PROMISE_FLAG_HANDLED)) cx->runtime()->removeUnhandledRejectedPromise(cx, promise); // Step 8.b. / 9.d. if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state)) return false; } // Step 10. promise->setFixedSlot(PromiseSlot_Flags, Int32Value(flags | PROMISE_FLAG_HANDLED)); // Step 11. return true; } static MOZ_MUST_USE bool AddPromiseReaction(JSContext* cx, Handle promise, Handle reaction) { MOZ_RELEASE_ASSERT(reaction->is()); RootedValue reactionVal(cx, ObjectValue(*reaction)); // The code that creates Promise reactions can handle wrapped Promises, // unwrapping them as needed. That means that the `promise` and `reaction` // objects we have here aren't necessarily from the same compartment. In // order to store the reaction on the promise, we have to ensure that it // is properly wrapped. mozilla::Maybe ar; if (promise->compartment() != cx->compartment()) { ar.emplace(cx, promise); if (!cx->compartment()->wrap(cx, &reactionVal)) return false; } // 25.4.5.3.1 steps 7.a,b. RootedValue reactionsVal(cx, promise->reactions()); if (reactionsVal.isUndefined()) { // If no reactions existed so far, just store the reaction record directly. promise->setFixedSlot(PromiseSlot_ReactionsOrResult, reactionVal); return true; } RootedObject reactionsObj(cx, &reactionsVal.toObject()); // If only a single reaction exists, it's stored directly instead of in a // list. In that case, `reactionsObj` might be a wrapper, which we can // always safely unwrap. if (IsProxy(reactionsObj)) { reactionsObj = UncheckedUnwrap(reactionsObj); if (JS_IsDeadWrapper(reactionsObj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } MOZ_RELEASE_ASSERT(reactionsObj->is()); } if (reactionsObj->is()) { // If a single reaction existed so far, create a list and store the // old and the new reaction in it. ArrayObject* reactions = NewDenseFullyAllocatedArray(cx, 2); if (!reactions) return false; reactions->setDenseInitializedLength(2); reactions->initDenseElement(0, reactionsVal); reactions->initDenseElement(1, reactionVal); promise->setFixedSlot(PromiseSlot_ReactionsOrResult, ObjectValue(*reactions)); } else { // Otherwise, just store the new reaction. MOZ_RELEASE_ASSERT(reactionsObj->is()); HandleNativeObject reactions = reactionsObj.as(); uint32_t len = reactions->getDenseInitializedLength(); DenseElementResult result = reactions->ensureDenseElements(cx, len, 1); if (result != DenseElementResult::Success) { MOZ_ASSERT(result == DenseElementResult::Failure); return false; } reactions->setDenseElement(len, reactionVal); } return true; } static MOZ_MUST_USE bool AddDummyPromiseReactionForDebugger(JSContext* cx, Handle promise, HandleObject dependentPromise) { if (promise->state() != JS::PromiseState::Pending) return true; // `dependentPromise` should be a maybe-wrapped Promise. MOZ_ASSERT(UncheckedUnwrap(dependentPromise)->is()); // Leave resolve and reject as null. Rooted capability(cx); capability.promise().set(dependentPromise); Rooted reaction(cx, NewReactionRecord(cx, capability, NullHandleValue, NullHandleValue, IncumbentGlobalObject::No)); if (!reaction) return false; reaction->setIsDebuggerDummy(); return AddPromiseReaction(cx, promise, reaction); } uint64_t PromiseObject::getID() { return PromiseDebugInfo::id(this); } double PromiseObject::lifetime() { return MillisecondsSinceStartup(mozilla::Some(mozilla::TimeStamp::Now())) - allocationTime(); } /** * Returns all promises that directly depend on this one. That means those * created by calling `then` on this promise, or the promise returned by * `Promise.all(iterable)` or `Promise.race(iterable)`, with this promise * being a member of the passed-in `iterable`. * * Per spec, we should have separate lists of reaction records for the * fulfill and reject cases. As an optimization, we have only one of those, * containing the required data for both cases. So we just walk that list * and extract the dependent promises from all reaction records. */ bool PromiseObject::dependentPromises(JSContext* cx, MutableHandle> values) { if (state() != JS::PromiseState::Pending) return true; RootedValue reactionsVal(cx, reactions()); // If no reactions are pending, we don't have list and are done. if (reactionsVal.isNullOrUndefined()) return true; RootedNativeObject reactions(cx, &reactionsVal.toObject().as()); // If only a single reaction is pending, it's stored directly. if (reactions->is()) { // Not all reactions have a Promise on them. RootedObject promiseObj(cx, reactions->as().promise()); if (!promiseObj) return true; if (!values.growBy(1)) return false; values[0].setObject(*promiseObj); return true; } uint32_t len = reactions->getDenseInitializedLength(); MOZ_ASSERT(len >= 2); uint32_t valuesIndex = 0; for (uint32_t i = 0; i < len; i++) { const Value& element = reactions->getDenseElement(i); PromiseReactionRecord* reaction = &element.toObject().as(); // Not all reactions have a Promise on them. RootedObject promiseObj(cx, reaction->promise()); if (!promiseObj) continue; if (!values.growBy(1)) return false; values[valuesIndex++].setObject(*promiseObj); } return true; } /* static */ bool PromiseObject::resolve(JSContext* cx, Handle promise, HandleValue resolutionValue) { MOZ_ASSERT(!PromiseHasAnyFlag(*promise, PROMISE_FLAG_ASYNC)); if (promise->state() != JS::PromiseState::Pending) return true; if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) return ResolvePromiseInternal(cx, promise, resolutionValue); JSFunction* resolveFun = GetResolveFunctionFromPromise(promise); if (!resolveFun) return true; RootedValue funVal(cx, ObjectValue(*resolveFun)); // For xray'd Promises, the resolve fun may have been created in another // compartment. For the call below to work in that case, wrap the // function into the current compartment. if (!cx->compartment()->wrap(cx, &funVal)) return false; RootedValue dummy(cx); return Call(cx, funVal, UndefinedHandleValue, resolutionValue, &dummy); } /* static */ bool PromiseObject::reject(JSContext* cx, Handle promise, HandleValue rejectionValue) { MOZ_ASSERT(!PromiseHasAnyFlag(*promise, PROMISE_FLAG_ASYNC)); if (promise->state() != JS::PromiseState::Pending) return true; if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS)) return ResolvePromise(cx, promise, rejectionValue, JS::PromiseState::Rejected); RootedValue funVal(cx, promise->getFixedSlot(PromiseSlot_RejectFunction)); MOZ_ASSERT(IsCallable(funVal)); RootedValue dummy(cx); return Call(cx, funVal, UndefinedHandleValue, rejectionValue, &dummy); } /* static */ void PromiseObject::onSettled(JSContext* cx, Handle promise) { PromiseDebugInfo::setResolutionInfo(cx, promise); if (promise->state() == JS::PromiseState::Rejected && promise->isUnhandled()) cx->runtime()->addUnhandledRejectedPromise(cx, promise); Debugger::onPromiseSettled(cx, promise); } 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(!IsInsideNursery(promiseCtor->lastProperty())); MOZ_ASSERT(!IsInsideNursery(speciesShape)); MOZ_ASSERT(!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() { - JS_POISON(this, 0xBB, sizeof(this), MemCheckKind::MakeUndefined); + JS_POISON(this, 0xBB, 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); } // We can skip `await` with an already resolved value only if the current frame // is the topmost JS frame and the current job is the last job in the job queue. // This guarantees that any new job enqueued in the current turn will be // executed immediately after the current job. // // Currently we only support skipping jobs when the async function is resumed // at least once. static MOZ_MUST_USE bool IsTopMostAsyncFunctionCall(JSContext* cx) { FrameIter iter(cx); // The current frame should be the async function. if (iter.done()) return false; if (!iter.calleeTemplate()) return false; MOZ_ASSERT(iter.calleeTemplate()->isAsync()); ++iter; // The parent frame should be the `next` function of the generator that is // internally called in AsyncFunctionResume. if (iter.done()) return false; if (!iter.calleeTemplate()) return false; if (!IsSelfHostedFunctionWithName(iter.calleeTemplate(), cx->names().GeneratorNext)) return false; ++iter; // There should be no more frames. if (iter.done()) return true; return false; } MOZ_MUST_USE bool js::TrySkipAwait(JSContext* cx, HandleValue val, bool* canSkip, MutableHandleValue resolved) { if (!cx->canSkipEnqueuingJobs) { *canSkip = false; return true; } if (!IsTopMostAsyncFunctionCall(cx)) { *canSkip = false; return true; } // Primitive values cannot be 'thenables', so we can trivially skip the // await operation. if (!val.isObject()) { resolved.set(val); *canSkip = true; return true; } JSObject* obj = &val.toObject(); if (!obj->is()) { *canSkip = false; return true; } PromiseObject* promise = &obj->as(); if (promise->state() == JS::PromiseState::Pending) { *canSkip = false; return true; } PromiseLookup& promiseLookup = cx->realm()->promiseLookup; if (!promiseLookup.isDefaultInstance(cx, promise)) { *canSkip = false; return true; } if (promise->state() == JS::PromiseState::Rejected) { // We don't optimize rejected Promises for now. *canSkip = false; return true; } resolved.set(promise->value()); *canSkip = true; return true; } OffThreadPromiseTask::OffThreadPromiseTask(JSContext* cx, Handle promise) : runtime_(cx->runtime()), promise_(cx, promise), registered_(false) { MOZ_ASSERT(runtime_ == promise_->zone()->runtimeFromMainThread()); MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_)); MOZ_ASSERT(cx->runtime()->offThreadPromiseState.ref().initialized()); } OffThreadPromiseTask::~OffThreadPromiseTask() { MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_)); OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref(); MOZ_ASSERT(state.initialized()); if (registered_) { LockGuard lock(state.mutex_); state.live_.remove(this); } } bool OffThreadPromiseTask::init(JSContext* cx) { MOZ_ASSERT(cx->runtime() == runtime_); MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_)); OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref(); MOZ_ASSERT(state.initialized()); LockGuard lock(state.mutex_); if (!state.live_.putNew(this)) { ReportOutOfMemory(cx); return false; } registered_ = true; return true; } void OffThreadPromiseTask::run(JSContext* cx, MaybeShuttingDown maybeShuttingDown) { MOZ_ASSERT(cx->runtime() == runtime_); MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_)); MOZ_ASSERT(registered_); MOZ_ASSERT(runtime_->offThreadPromiseState.ref().initialized()); if (maybeShuttingDown == JS::Dispatchable::NotShuttingDown) { // We can't leave a pending exception when returning to the caller so do // the same thing as Gecko, which is to ignore the error. This should // only happen due to OOM or interruption. AutoRealm ar(cx, promise_); if (!resolve(cx, promise_)) cx->clearPendingException(); } js_delete(this); } void OffThreadPromiseTask::dispatchResolveAndDestroy() { MOZ_ASSERT(registered_); OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref(); MOZ_ASSERT(state.initialized()); MOZ_ASSERT((LockGuard(state.mutex_), state.live_.has(this))); // If the dispatch succeeds, then we are guaranteed that run() will be // called on an active JSContext of runtime_. if (state.dispatchToEventLoopCallback_(state.dispatchToEventLoopClosure_, this)) return; // We assume, by interface contract, that if the dispatch fails, it's // because the embedding is in the process of shutting down the JSRuntime. // Since JSRuntime destruction calls shutdown(), we can rely on shutdown() // to delete the task on its active JSContext thread. shutdown() waits for // numCanceled_ == live_.length, so we notify when this condition is // reached. LockGuard lock(state.mutex_); state.numCanceled_++; if (state.numCanceled_ == state.live_.count()) state.allCanceled_.notify_one(); } OffThreadPromiseRuntimeState::OffThreadPromiseRuntimeState() : dispatchToEventLoopCallback_(nullptr), dispatchToEventLoopClosure_(nullptr), mutex_(mutexid::OffThreadPromiseState), numCanceled_(0), internalDispatchQueueClosed_(false) { } OffThreadPromiseRuntimeState::~OffThreadPromiseRuntimeState() { MOZ_ASSERT(live_.empty()); MOZ_ASSERT(numCanceled_ == 0); MOZ_ASSERT(internalDispatchQueue_.empty()); MOZ_ASSERT(!initialized()); } void OffThreadPromiseRuntimeState::init(JS::DispatchToEventLoopCallback callback, void* closure) { MOZ_ASSERT(!initialized()); dispatchToEventLoopCallback_ = callback; dispatchToEventLoopClosure_ = closure; MOZ_ASSERT(initialized()); } /* static */ bool OffThreadPromiseRuntimeState::internalDispatchToEventLoop(void* closure, JS::Dispatchable* d) { OffThreadPromiseRuntimeState& state = *reinterpret_cast(closure); MOZ_ASSERT(state.usingInternalDispatchQueue()); LockGuard lock(state.mutex_); if (state.internalDispatchQueueClosed_) return false; // The JS API contract is that 'false' means shutdown, so be infallible // here (like Gecko). AutoEnterOOMUnsafeRegion noOOM; if (!state.internalDispatchQueue_.append(d)) noOOM.crash("internalDispatchToEventLoop"); // Wake up internalDrain() if it is waiting for a job to finish. state.internalDispatchQueueAppended_.notify_one(); return true; } bool OffThreadPromiseRuntimeState::usingInternalDispatchQueue() const { return dispatchToEventLoopCallback_ == internalDispatchToEventLoop; } void OffThreadPromiseRuntimeState::initInternalDispatchQueue() { init(internalDispatchToEventLoop, this); MOZ_ASSERT(usingInternalDispatchQueue()); } bool OffThreadPromiseRuntimeState::initialized() const { return !!dispatchToEventLoopCallback_; } void OffThreadPromiseRuntimeState::internalDrain(JSContext* cx) { MOZ_ASSERT(usingInternalDispatchQueue()); MOZ_ASSERT(!internalDispatchQueueClosed_); while (true) { DispatchableVector dispatchQueue; { LockGuard lock(mutex_); MOZ_ASSERT_IF(!internalDispatchQueue_.empty(), !live_.empty()); if (live_.empty()) return; while (internalDispatchQueue_.empty()) internalDispatchQueueAppended_.wait(lock); Swap(dispatchQueue, internalDispatchQueue_); MOZ_ASSERT(internalDispatchQueue_.empty()); } // Don't call run() with mutex_ held to avoid deadlock. for (JS::Dispatchable* d : dispatchQueue) d->run(cx, JS::Dispatchable::NotShuttingDown); } } bool OffThreadPromiseRuntimeState::internalHasPending() { MOZ_ASSERT(usingInternalDispatchQueue()); MOZ_ASSERT(!internalDispatchQueueClosed_); LockGuard lock(mutex_); MOZ_ASSERT_IF(!internalDispatchQueue_.empty(), !live_.empty()); return !live_.empty(); } void OffThreadPromiseRuntimeState::shutdown(JSContext* cx) { if (!initialized()) return; // When the shell is using the internal event loop, we must simulate our // requirement of the embedding that, before shutdown, all successfully- // dispatched-to-event-loop tasks have been run. if (usingInternalDispatchQueue()) { DispatchableVector dispatchQueue; { LockGuard lock(mutex_); Swap(dispatchQueue, internalDispatchQueue_); MOZ_ASSERT(internalDispatchQueue_.empty()); internalDispatchQueueClosed_ = true; } // Don't call run() with mutex_ held to avoid deadlock. for (JS::Dispatchable* d : dispatchQueue) d->run(cx, JS::Dispatchable::ShuttingDown); } { // Wait until all live OffThreadPromiseRuntimeState have been confirmed // canceled by OffThreadPromiseTask::dispatchResolve(). LockGuard lock(mutex_); while (live_.count() != numCanceled_) { MOZ_ASSERT(numCanceled_ < live_.count()); allCanceled_.wait(lock); } } // Now that all the tasks have stopped concurrent execution, we can just // delete everything. We don't want each OffThreadPromiseTask to unregister // itself (which would mutate live_ while we are iterating over it) so reset // the tasks' internal registered_ flag. for (OffThreadPromiseTaskSet::Range r = live_.all(); !r.empty(); r.popFront()) { OffThreadPromiseTask* task = r.front(); MOZ_ASSERT(task->registered_); task->registered_ = false; js_delete(task); } live_.clear(); numCanceled_ = 0; // After shutdown, there should be no OffThreadPromiseTask activity in this // JSRuntime. Revert to the !initialized() state to catch bugs. dispatchToEventLoopCallback_ = nullptr; MOZ_ASSERT(!initialized()); } static JSObject* CreatePromisePrototype(JSContext* cx, JSProtoKey key) { return GlobalObject::createBlankPrototype(cx, cx->global(), &PromiseObject::protoClass_); } const JSJitInfo promise_then_info = { { (JSJitGetterOp)Promise_then_noRetVal }, { 0 }, /* unused */ { 0 }, /* unused */ JSJitInfo::IgnoresReturnValueNative, JSJitInfo::AliasEverything, JSVAL_TYPE_UNDEFINED, }; const JSJitInfo promise_catch_info = { { (JSJitGetterOp)Promise_catch_noRetVal }, { 0 }, /* unused */ { 0 }, /* unused */ JSJitInfo::IgnoresReturnValueNative, JSJitInfo::AliasEverything, JSVAL_TYPE_UNDEFINED, }; static const JSFunctionSpec promise_methods[] = { JS_FNINFO("then", Promise_then, &promise_then_info, 2, 0), JS_FNINFO("catch", Promise_catch, &promise_catch_info, 1, 0), JS_SELF_HOSTED_FN("finally", "Promise_finally", 1, 0), JS_FS_END }; static const JSPropertySpec promise_properties[] = { JS_STRING_SYM_PS(toStringTag, "Promise", JSPROP_READONLY), JS_PS_END }; static const JSFunctionSpec promise_static_methods[] = { JS_FN("all", Promise_static_all, 1, 0), JS_FN("race", Promise_static_race, 1, 0), JS_FN("reject", Promise_reject, 1, 0), JS_FN("resolve", Promise_static_resolve, 1, 0), JS_FS_END }; static const JSPropertySpec promise_static_properties[] = { JS_SYM_GET(species, Promise_static_species, 0), JS_PS_END }; static const ClassSpec PromiseObjectClassSpec = { GenericCreateConstructor, CreatePromisePrototype, promise_static_methods, promise_static_properties, promise_methods, promise_properties }; const Class PromiseObject::class_ = { "Promise", JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Promise) | JSCLASS_HAS_XRAYED_CONSTRUCTOR, JS_NULL_CLASS_OPS, &PromiseObjectClassSpec }; const Class PromiseObject::protoClass_ = { "PromiseProto", JSCLASS_HAS_CACHED_PROTO(JSProto_Promise), JS_NULL_CLASS_OPS, &PromiseObjectClassSpec };