/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "vm/TupleType.h" #include "mozilla/FloatingPoint.h" #include "mozilla/HashFunctions.h" #include "jsapi.h" #include "builtin/TupleObject.h" #include "gc/Allocator.h" #include "gc/AllocKind.h" #include "js/TypeDecls.h" #include "js/Value.h" #include "util/StringBuffer.h" #include "vm/EqualityOperations.h" #include "vm/GlobalObject.h" #include "vm/JSContext.h" #include "vm/RecordTupleShared.h" #include "vm/RecordType.h" #include "vm/SelfHosting.h" #include "vm/ToSource.h" #include "vm/GeckoProfiler-inl.h" #include "vm/JSObject-inl.h" #include "vm/NativeObject-inl.h" using namespace js; static bool TupleConstructor(JSContext* cx, unsigned argc, Value* vp); static const JSFunctionSpec tuple_static_methods[] = { JS_FN("isTuple", tuple_is_tuple, 1, 0), JS_SELF_HOSTED_FN("from", "TupleFrom", 1, 0), JS_FN("of", tuple_of, 0, 0), JS_FS_END}; static const JSFunctionSpec tuple_methods[] = { JS_SELF_HOSTED_FN("toSorted", "TupleToSorted", 1, 0), JS_SELF_HOSTED_FN("toSpliced", "TupleToSpliced", 2, 0), JS_SELF_HOSTED_FN("concat", "TupleConcat", 0, 0), JS_SELF_HOSTED_FN("includes", "TupleIncludes", 1, 0), JS_SELF_HOSTED_FN("indexOf", "TupleIndexOf", 1, 0), JS_SELF_HOSTED_FN("join", "TupleJoin", 1, 0), JS_SELF_HOSTED_FN("lastIndexOf", "TupleLastIndexOf", 1, 0), JS_SELF_HOSTED_FN("toLocaleString", "TupleToLocaleString", 2, 0), JS_SELF_HOSTED_FN("toString", "TupleToString", 0, 0), JS_SELF_HOSTED_FN("entries", "TupleEntries", 0, 0), JS_SELF_HOSTED_FN("every", "TupleEvery", 1, 0), JS_SELF_HOSTED_FN("filter", "TupleFilter", 1, 0), JS_SELF_HOSTED_FN("find", "TupleFind", 1, 0), JS_SELF_HOSTED_FN("findIndex", "TupleFindIndex", 1, 0), JS_SELF_HOSTED_FN("forEach", "TupleForEach", 1, 0), JS_SELF_HOSTED_FN("keys", "TupleKeys", 0, 0), JS_SELF_HOSTED_FN("map", "TupleMap", 1, 0), JS_SELF_HOSTED_FN("reduce", "TupleReduce", 1, 0), JS_SELF_HOSTED_FN("reduceRight", "TupleReduceRight", 1, 0), JS_SELF_HOSTED_FN("some", "TupleSome", 1, 0), JS_SELF_HOSTED_FN("values", "$TupleValues", 0, 0), JS_SELF_HOSTED_SYM_FN(iterator, "$TupleValues", 0, 0), JS_SELF_HOSTED_FN("flat", "TupleFlat", 0, 0), JS_SELF_HOSTED_FN("flatMap", "TupleFlatMap", 1, 0), JS_SELF_HOSTED_FN("toReversed", "TupleToReversed", 0, 0), JS_FN("with", tuple_with, 2, 0), JS_FN("slice", tuple_slice, 2, 0), JS_FN("valueOf", tuple_value_of, 0, 0), JS_FS_END}; Shape* TupleType::getInitialShape(JSContext* cx) { return SharedShape::getInitialShape(cx, &TupleType::class_, cx->realm(), TaggedProto(nullptr), 0); // tuples don't have slots, but only integer-indexed elements. } // Prototype methods // Proposal // Tuple.prototype.with() bool js::tuple_with(JSContext* cx, unsigned argc, Value* vp) { AutoGeckoProfilerEntry pseudoFrame( cx, "Tuple.prototype.with", JS::ProfilingCategoryPair::JS, uint32_t(ProfilingStackFrame::Flags::RELEVANT_FOR_JS)); CallArgs args = CallArgsFromVp(argc, vp); /* Step 1. */ RootedValue v(cx, args.thisv()); mozilla::Maybe maybeTuple = js::ThisTupleValue(cx, v); if (!maybeTuple) { return false; } Rooted tuple(cx, &(*maybeTuple)); /* Step 2. */ uint64_t length = tuple->getDenseInitializedLength(); TupleType* list = TupleType::createUninitialized(cx, length); if (!list) { return false; } /* Step 4 */ uint64_t index; if (!ToIndex(cx, args.get(0), JSMSG_BAD_TUPLE_INDEX, &index)) { return false; } /* Step 5 */ if (index >= length) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_TUPLE_INDEX, "Tuple.with"); return false; } /* Step 6 */ RootedValue value(cx, args.get(1)); if (value.isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_RECORD_TUPLE_NO_OBJECT, "Tuple.with"); return false; } /* Step 7 */ uint64_t before = index; uint64_t after = length - index - 1; list->copyDenseElements(0, tuple->getDenseElements(), before); list->setDenseInitializedLength(index + 1); list->initDenseElement(index, value); list->copyDenseElements( index + 1, tuple->getDenseElements() + uint32_t(index + 1), after); list->setDenseInitializedLength(length); list->finishInitialization(cx); /* Step 8 */ args.rval().setExtendedPrimitive(*list); return true; } // Proposal // Tuple.prototype.slice() bool js::tuple_slice(JSContext* cx, unsigned argc, Value* vp) { AutoGeckoProfilerEntry pseudoFrame( cx, "Tuple.prototype.slice", JS::ProfilingCategoryPair::JS, uint32_t(ProfilingStackFrame::Flags::RELEVANT_FOR_JS)); CallArgs args = CallArgsFromVp(argc, vp); RootedValue v(cx, args.thisv()); /* Steps 1-2. */ mozilla::Maybe maybeList = js::ThisTupleValue(cx, v); if (!maybeList) { return false; } Rooted list(cx, &(*maybeList)); /* Step 3. */ uint32_t len = list->getDenseInitializedLength(); /* Step 4. */ double relativeStart; if (!ToInteger(cx, args.get(0), &relativeStart)) { return false; } /* Step 5. */ uint32_t k; if (relativeStart < 0.0) { k = std::max(len + relativeStart, 0.0); } else { k = std::min(relativeStart, double(len)); } /* Step 6. */ double relativeEnd; if (argc > 1 && !args.get(1).isUndefined()) { if (!ToInteger(cx, args.get(1), &relativeEnd)) { return false; } } else { relativeEnd = len; } /* Step 7. */ uint32_t finalIndex; if (relativeEnd < 0.0) { finalIndex = std::max(len + relativeEnd, 0.0); } else { finalIndex = std::min(relativeEnd, double(len)); } /* Step 8. */ uint32_t newLen = finalIndex >= k ? finalIndex - k : 0; TupleType* newList = TupleType::createUninitialized(cx, newLen); if (!newList) { return false; } /* Step 9. */ HeapSlotArray oldElements = list->getDenseElements(); newList->copyDenseElements(0, oldElements + k, newLen); newList->setDenseInitializedLength(newLen); newList->finishInitialization(cx); /* Step 10. */ args.rval().setExtendedPrimitive(*newList); return true; } // Proposal // Tuple.prototype.valueOf() bool js::tuple_value_of(JSContext* cx, unsigned argc, Value* vp) { AutoGeckoProfilerEntry pseudoFrame( cx, "Tuple.prototype.valueOf", JS::ProfilingCategoryPair::JS, uint32_t(ProfilingStackFrame::Flags::RELEVANT_FOR_JS)); CallArgs args = CallArgsFromVp(argc, vp); /* Step 1. */ HandleValue thisv = args.thisv(); mozilla::Maybe tuple = js::ThisTupleValue(cx, thisv); if (!tuple) { return false; } args.rval().setExtendedPrimitive(*tuple); return true; } bool TupleType::copy(JSContext* cx, Handle in, MutableHandle out) { out.set(TupleType::createUninitialized(cx, in->length())); if (!out) { return false; } RootedValue v(cx), vCopy(cx); for (uint32_t i = 0; i < in->length(); i++) { // Let v = in[i] v.set(in->getDenseElement(i)); // Copy v if (!CopyRecordTupleElement(cx, v, &vCopy)) { return false; } // Set result[i] to v if (!out->initializeNextElement(cx, vCopy)) { return false; } } out->finishInitialization(cx); return true; } TupleType* TupleType::create(JSContext* cx, uint32_t length, const Value* elements) { for (uint32_t index = 0; index < length; index++) { if (!elements[index].isPrimitive()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_RECORD_TUPLE_NO_OBJECT); return nullptr; } } TupleType* tup = TupleType::createUninitialized(cx, length); if (!tup) { return nullptr; } tup->initDenseElements(elements, length); tup->finishInitialization(cx); return tup; } static TupleType* allocate(JSContext* cx, gc::AllocKind allocKind) { Rooted shape(cx, TupleType::getInitialShape(cx)); if (!shape) { return nullptr; } TupleType* tup = cx->newCell(allocKind, gc::Heap::Default, &TupleType::class_); if (!tup) { return nullptr; } tup->initShape(shape); tup->initEmptyDynamicSlots(); tup->initFixedElements(allocKind, 0); return tup; } TupleType* TupleType::createUninitialized(JSContext* cx, uint32_t length) { gc::AllocKind allocKind = GuessArrayGCKind(length); TupleType* tup = allocate(cx, allocKind); if (!tup) { return nullptr; } if (!tup->ensureElements(cx, length)) { return nullptr; } return tup; } bool TupleType::initializeNextElement(JSContext* cx, HandleValue elt) { if (!elt.isPrimitive()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_RECORD_TUPLE_NO_OBJECT); return false; } uint32_t length = getDenseInitializedLength(); if (!ensureElements(cx, length + 1)) { return false; } setDenseInitializedLength(length + 1); initDenseElement(length, elt); return true; } void TupleType::finishInitialization(JSContext* cx) { shrinkCapacityToInitializedLength(cx); ObjectElements* header = getElementsHeader(); header->length = header->initializedLength; header->setNotExtensible(); header->seal(); header->freeze(); } bool TupleType::getOwnProperty(HandleId id, MutableHandleValue vp) const { if (!id.isInt()) { return false; } int32_t index = id.toInt(); if (index < 0 || uint32_t(index) >= length()) { return false; } vp.set(getDenseElement(index)); return true; } js::HashNumber TupleType::hash(const TupleType::ElementHasher& hasher) const { MOZ_ASSERT(isAtomized()); js::HashNumber h = mozilla::HashGeneric(length()); for (uint32_t i = 0; i < length(); i++) { h = mozilla::AddToHash(h, hasher(getDenseElement(i))); } return h; } bool TupleType::ensureAtomized(JSContext* cx) { if (isAtomized()) { return true; } RootedValue child(cx); bool changed; for (uint32_t i = 0; i < length(); i++) { child.set(getDenseElement(i)); if (!EnsureAtomized(cx, &child, &changed)) { return false; } if (changed) { // We cannot use setDenseElement(), because this object is frozen. elements_[i].set(this, HeapSlot::Element, unshiftedIndex(i), child); } } getElementsHeader()->setTupleIsAtomized(); return true; } bool TupleType::sameValueZero(JSContext* cx, TupleType* lhs, TupleType* rhs, bool* equal) { return sameValueWith(cx, lhs, rhs, equal); } bool TupleType::sameValue(JSContext* cx, TupleType* lhs, TupleType* rhs, bool* equal) { return sameValueWith(cx, lhs, rhs, equal); } bool TupleType::sameValueZero(TupleType* lhs, TupleType* rhs) { MOZ_ASSERT(lhs->isAtomized()); MOZ_ASSERT(rhs->isAtomized()); if (lhs == rhs) { return true; } if (lhs->length() != rhs->length()) { return false; } Value v1, v2; for (uint32_t index = 0; index < lhs->length(); index++) { v1 = lhs->getDenseElement(index); v2 = rhs->getDenseElement(index); if (!js::SameValueZeroLinear(v1, v2)) { return false; } } return true; } template bool TupleType::sameValueWith(JSContext* cx, TupleType* lhs, TupleType* rhs, bool* equal) { MOZ_ASSERT(lhs->getElementsHeader()->isFrozen()); MOZ_ASSERT(rhs->getElementsHeader()->isFrozen()); if (lhs == rhs) { *equal = true; return true; } if (lhs->length() != rhs->length()) { *equal = false; return true; } *equal = true; RootedValue v1(cx); RootedValue v2(cx); for (uint32_t index = 0; index < lhs->length(); index++) { v1.set(lhs->getDenseElement(index)); v2.set(rhs->getDenseElement(index)); if (!Comparator(cx, v1, v2, equal)) { return false; } if (!*equal) { return true; } } return true; } JSString* js::TupleToSource(JSContext* cx, Handle tup) { JSStringBuilder sb(cx); if (!sb.append("#[")) { return nullptr; } uint32_t length = tup->length(); RootedValue elt(cx); for (uint32_t index = 0; index < length; index++) { elt.set(tup->getDenseElement(index)); /* Get element's character string. */ JSString* str = ValueToSource(cx, elt); if (!str) { return nullptr; } /* Append element to buffer. */ if (!sb.append(str)) { return nullptr; } if (index + 1 != length) { if (!sb.append(", ")) { return nullptr; } } } /* Finalize the buffer. */ if (!sb.append(']')) { return nullptr; } return sb.finishString(); } // Record and Tuple proposal section 9.2.1 bool TupleConstructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Step 1. if (args.isConstructing()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_CONSTRUCTOR, "Tuple"); return false; } TupleType* tup = TupleType::create(cx, args.length(), args.array()); if (!tup) { return false; } args.rval().setExtendedPrimitive(*tup); return true; } /*===========================================================================*\ BEGIN: Tuple.prototype methods \*===========================================================================*/ static bool ArrayToTuple(JSContext* cx, const CallArgs& args) { Rooted aObj(cx, &args.rval().toObject().as()); TupleType* tup = TupleType::createUnchecked(cx, aObj); if (!tup) { return false; } args.rval().setExtendedPrimitive(*tup); return true; } // Takes an array as a single argument and returns a tuple of the // array elements. This method copies the array, because the callee // may still hold a pointer to it and it would break garbage collection // to change the type of the object from ArrayObject to TupleType (which // is the only way to re-use the same object if it has fixed elements.) // Should only be called from self-hosted tuple methods; // assumes all elements are non-objects and the array is packed bool js::tuple_construct(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args[0].toObject().is()); args.rval().set(args[0]); return ArrayToTuple(cx, args); } bool js::tuple_is_tuple(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return IsTupleUnchecked(cx, args); } TupleType* TupleType::createUnchecked(JSContext* cx, Handle aObj) { size_t len = aObj->getDenseInitializedLength(); MOZ_ASSERT(aObj->getElementsHeader()->numShiftedElements() == 0); TupleType* tup = createUninitialized(cx, len); if (!tup) { return nullptr; } tup->initDenseElements(aObj, 0, len); tup->finishInitialization(cx); return tup; } bool js::tuple_of(JSContext* cx, unsigned argc, Value* vp) { /* Step 1 */ CallArgs args = CallArgsFromVp(argc, vp); size_t len = args.length(); Value* items = args.array(); /* Step 2 */ for (size_t i = 0; i < len; i++) { if (items[i].isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_RECORD_TUPLE_NO_OBJECT, "Tuple.of"); return false; } } /* Step 3 */ ArrayObject* result = js::NewDenseCopiedArray(cx, len, items, GenericObject); if (!result) { return false; } args.rval().setObject(*result); /* Step 4 */ return ArrayToTuple(cx, args); } bool js::IsTuple(const Value& v) { if (v.isExtendedPrimitive()) return v.toExtendedPrimitive().is(); if (v.isObject()) return v.toObject().is(); return false; } // Caller is responsible for rooting the result TupleType& TupleType::thisTupleValue(const Value& val) { MOZ_ASSERT(IsTuple(val)); return (val.isExtendedPrimitive() ? val.toExtendedPrimitive().as() : val.toObject().as().unbox()); } bool HandleIsTuple(HandleValue v) { return IsTuple(v.get()); } // 8.2.3.2 get Tuple.prototype.length bool lengthAccessor_impl(JSContext* cx, const CallArgs& args) { // Step 1. TupleType& tuple = TupleType::thisTupleValue(args.thisv().get()); // Step 2. args.rval().setInt32(tuple.length()); return true; } bool TupleType::lengthAccessor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /*===========================================================================*\ END: Tuple.prototype methods \*===========================================================================*/ const JSClass TupleType::class_ = {"tuple", 0, JS_NULL_CLASS_OPS, &TupleType::classSpec_}; const JSClass TupleType::protoClass_ = { "Tuple.prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_Tuple), JS_NULL_CLASS_OPS, &TupleType::classSpec_}; /* static */ const JSPropertySpec properties_[] = { JS_STRING_SYM_PS(toStringTag, "Tuple", JSPROP_READONLY), JS_PSG("length", TupleType::lengthAccessor, 0), JS_PS_END}; const ClassSpec TupleType::classSpec_ = { GenericCreateConstructor, GenericCreatePrototype, tuple_static_methods, nullptr, tuple_methods, properties_, nullptr};