From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- js/src/vm/RecordType.cpp | 538 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 538 insertions(+) create mode 100644 js/src/vm/RecordType.cpp (limited to 'js/src/vm/RecordType.cpp') diff --git a/js/src/vm/RecordType.cpp b/js/src/vm/RecordType.cpp new file mode 100644 index 0000000000..3523f14252 --- /dev/null +++ b/js/src/vm/RecordType.cpp @@ -0,0 +1,538 @@ +/* -*- 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/RecordType.h" + +#include "mozilla/Assertions.h" + +#include "jsapi.h" + +#include "gc/Nursery.h" +#include "js/Array.h" +#include "js/TypeDecls.h" +#include "js/Value.h" +#include "util/StringBuffer.h" +#include "vm/ArrayObject.h" +#include "vm/EqualityOperations.h" +#include "vm/JSAtomUtils.h" // AtomizeString, EnsureAtomized +#include "vm/JSContext.h" +#include "vm/JSObject.h" +#include "vm/NativeObject.h" +#include "vm/ObjectFlags.h" +#include "vm/PropertyInfo.h" +#include "vm/PropMap.h" +#include "vm/RecordTupleShared.h" +#include "vm/StringType.h" +#include "vm/ToSource.h" +#include "vm/TupleType.h" + +#include "vm/JSAtomUtils-inl.h" // AtomToId +#include "vm/JSObject-inl.h" +#include "vm/NativeObject-inl.h" + +using namespace js; + +static bool RecordConstructor(JSContext* cx, unsigned argc, Value* vp); + +const JSClass RecordType::class_ = {"record", + JSCLASS_HAS_RESERVED_SLOTS(SLOT_COUNT), + JS_NULL_CLASS_OPS, &RecordType::classSpec_}; + +const ClassSpec RecordType::classSpec_ = { + GenericCreateConstructor, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr}; + +Shape* RecordType::getInitialShape(JSContext* cx) { + return SharedShape::getInitialShape(cx, &RecordType::class_, cx->realm(), + TaggedProto(nullptr), SLOT_COUNT); +} + +bool RecordType::copy(JSContext* cx, Handle in, + MutableHandle out) { + uint32_t len = in->length(); + out.set(RecordType::createUninitialized(cx, len)); + if (!out) { + return false; + } + RootedId k(cx); + RootedValue v(cx), vCopy(cx); + ArrayObject& sortedKeys = in->getFixedSlot(RecordType::SORTED_KEYS_SLOT) + .toObject() + .as(); + for (uint32_t i = 0; i < len; i++) { + // Get the ith record key and convert it to a string, then to an id `k` + Value kVal = sortedKeys.getDenseElement(i); + MOZ_ASSERT(kVal.isString()); + k.set(AtomToId(&kVal.toString()->asAtom())); + cx->markId(k); + + // Get the value corresponding to `k` + MOZ_ALWAYS_TRUE(in->getOwnProperty(cx, k, &v)); + + // Copy `v` for the new record + if (!CopyRecordTupleElement(cx, v, &vCopy)) { + return false; + } + + // Set `k` to `v` in the new record + if (!out->initializeNextProperty(cx, k, vCopy)) { + return false; + } + } + return out->finishInitialization(cx); +} + +uint32_t RecordType::length() { + ArrayObject& sortedKeys = + getFixedSlot(SORTED_KEYS_SLOT).toObject().as(); + + return sortedKeys.getDenseInitializedLength(); +} + +RecordType* RecordType::createUninitialized(JSContext* cx, + uint32_t initialLength) { + Rooted shape(cx, getInitialShape(cx)); + if (!shape) { + return nullptr; + } + + Rooted rec( + cx, cx->newCell(NewObjectGCKind(), gc::Heap::Default, + &RecordType::class_)); + if (!rec) { + return nullptr; + } + rec->initShape(shape); + rec->setEmptyElements(); + rec->initEmptyDynamicSlots(); + rec->initFixedSlots(SLOT_COUNT); + + Rooted sortedKeys( + cx, NewDenseFullyAllocatedArray(cx, initialLength)); + if (!sortedKeys) { + return nullptr; + } + + rec->initFixedSlot(SORTED_KEYS_SLOT, ObjectValue(*sortedKeys)); + rec->initFixedSlot(IS_ATOMIZED_SLOT, BooleanValue(false)); + + return rec; +} + +bool RecordType::initializeNextProperty(JSContext* cx, HandleId key, + HandleValue value) { + if (key.isSymbol()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_RECORD_NO_SYMBOL_KEY); + return false; + } + + if (!value.isPrimitive()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_RECORD_TUPLE_NO_OBJECT); + return false; + } + + mozilla::Maybe prop = lookupPure(key); + + if (prop.isSome()) { + MOZ_ASSERT(prop.value().hasSlot()); + setSlot(prop.value().slot(), value); + return true; + } + + constexpr PropertyFlags propFlags = {PropertyFlag::Enumerable}; + Rooted target(cx, this); + uint32_t slot; + if (!NativeObject::addProperty(cx, target, key, propFlags, &slot)) { + return false; + } + initSlot(slot, value); + + // Add the key to the SORTED_KEYS internal slot + + JSAtom* atomKey = key.isString() ? AtomizeString(cx, key.toString()) + : Int32ToAtom(cx, key.toInt()); + if (!atomKey) { + return false; + } + + ArrayObject* sortedKeys = + &getFixedSlot(SORTED_KEYS_SLOT).toObject().as(); + uint32_t initializedLength = sortedKeys->getDenseInitializedLength(); + + if (!sortedKeys->ensureElements(cx, initializedLength + 1)) { + return false; + } + sortedKeys->setDenseInitializedLength(initializedLength + 1); + sortedKeys->initDenseElement(initializedLength, StringValue(atomKey)); + + return true; +} + +bool RecordType::finishInitialization(JSContext* cx) { + Rooted obj(cx, this); + if (!JSObject::setFlag(cx, obj, ObjectFlag::NotExtensible)) { + return false; + } + if (!ObjectElements::FreezeOrSeal(cx, obj, IntegrityLevel::Frozen)) { + return false; + } + + ArrayObject& sortedKeys = + getFixedSlot(SORTED_KEYS_SLOT).toObject().as(); + uint32_t length = sortedKeys.getDenseInitializedLength(); + + Rooted tmpKey(cx); + + // Sort the keys. This is insertion sort - O(n^2) - but it's ok for now + // becase records are probably not too big anyway. + for (uint32_t i = 1, j; i < length; i++) { +#define KEY(index) sortedKeys.getDenseElement(index) +#define KEY_S(index) &KEY(index).toString()->asLinear() + + MOZ_ASSERT(KEY(i).isString()); + MOZ_ASSERT(KEY(i).toString()->isLinear()); + + tmpKey = KEY_S(i); + + for (j = i; j > 0 && CompareStrings(KEY_S(j - 1), tmpKey) > 0; j--) { + sortedKeys.setDenseElement(j, KEY(j - 1)); + } + + sortedKeys.setDenseElement(j, StringValue(tmpKey)); + +#undef KEY +#undef KEY_S + } + + // We preallocate 1 element for each object spread. If spreads end up + // introducing zero elements, we can then shrink the sortedKeys array. + sortedKeys.setDenseInitializedLength(length); + sortedKeys.setLength(length); + sortedKeys.setNonWritableLength(cx); + + MOZ_ASSERT(sortedKeys.length() == length); + + return true; +} + +bool RecordType::getOwnProperty(JSContext* cx, HandleId id, + MutableHandleValue vp) const { + if (id.isSymbol()) { + return false; + } + + uint32_t index; + + // Check for a native dense element. + if (id.isInt()) { + index = id.toInt(); + if (containsDenseElement(index)) { + vp.set(getDenseElement(index)); + return true; + } + } + + // Check for a native property. + if (PropMap* map = shape()->lookup(cx, id, &index)) { + PropertyInfo info = map->getPropertyInfo(index); + MOZ_ASSERT(info.isDataProperty()); + vp.set(getSlot(info.slot())); + return true; + } + + return false; +} + +js::HashNumber RecordType::hash(const RecordType::FieldHasher& hasher) { + MOZ_ASSERT(isAtomized()); + + ArrayObject& sortedKeys = + getFixedSlot(SORTED_KEYS_SLOT).toObject().as(); + uint32_t length = sortedKeys.length(); + + js::HashNumber h = mozilla::HashGeneric(length); + for (uint32_t i = 0; i < length; i++) { + JSAtom& key = sortedKeys.getDenseElement(i).toString()->asAtom(); + + mozilla::Maybe prop = lookupPure(AtomToId(&key)); + MOZ_ASSERT(prop.isSome() && prop.value().hasSlot()); + + h = mozilla::AddToHash(h, key.hash(), hasher(getSlot(prop.value().slot()))); + } + + return h; +} + +bool RecordType::ensureAtomized(JSContext* cx) { + if (isAtomized()) { + return true; + } + + ArrayObject& sortedKeys = + getFixedSlot(SORTED_KEYS_SLOT).toObject().as(); + uint32_t length = sortedKeys.length(); + + RootedValue child(cx); + bool updated; + for (uint32_t i = 0; i < length; i++) { + JSAtom& key = sortedKeys.getDenseElement(i).toString()->asAtom(); + + mozilla::Maybe prop = lookupPure(AtomToId(&key)); + MOZ_ASSERT(prop.isSome() && prop.value().hasSlot()); + uint32_t slot = prop.value().slot(); + + child.set(getSlot(slot)); + + if (!EnsureAtomized(cx, &child, &updated)) { + return false; + } + if (updated) { + setSlot(slot, child); + } + } + + setFixedSlot(IS_ATOMIZED_SLOT, BooleanValue(true)); + + return true; +} + +bool RecordType::sameValueZero(JSContext* cx, RecordType* lhs, RecordType* rhs, + bool* equal) { + return sameValueWith(cx, lhs, rhs, equal); +} + +bool RecordType::sameValue(JSContext* cx, RecordType* lhs, RecordType* rhs, + bool* equal) { + return sameValueWith(cx, lhs, rhs, equal); +} + +bool RecordType::sameValueZero(RecordType* lhs, RecordType* rhs) { + MOZ_ASSERT(lhs->isAtomized()); + MOZ_ASSERT(rhs->isAtomized()); + + if (lhs == rhs) { + return true; + } + + uint32_t length = lhs->length(); + + if (rhs->length() != length) { + return false; + } + + ArrayObject& lhsSortedKeys = + lhs->getFixedSlot(SORTED_KEYS_SLOT).toObject().as(); + ArrayObject& rhsSortedKeys = + rhs->getFixedSlot(SORTED_KEYS_SLOT).toObject().as(); + + Value v1, v2; + + for (uint32_t index = 0; index < length; index++) { + JSAtom* key = &lhsSortedKeys.getDenseElement(index).toString()->asAtom(); + if (!EqualStrings( + key, &rhsSortedKeys.getDenseElement(index).toString()->asAtom())) { + return false; + } + + { + mozilla::Maybe lhsProp = lhs->lookupPure(AtomToId(key)); + MOZ_ASSERT(lhsProp.isSome() && lhsProp.value().hasSlot()); + v1 = lhs->getSlot(lhsProp.value().slot()); + } + + { + mozilla::Maybe rhsProp = rhs->lookupPure(AtomToId(key)); + MOZ_ASSERT(rhsProp.isSome() && rhsProp.value().hasSlot()); + v2 = rhs->getSlot(rhsProp.value().slot()); + } + + if (!js::SameValueZeroLinear(v1, v2)) { + return false; + } + } + + return true; +} + +template +bool RecordType::sameValueWith(JSContext* cx, RecordType* lhs, RecordType* rhs, + bool* equal) { + if (lhs == rhs) { + *equal = true; + return true; + } + + uint32_t length = lhs->length(); + + if (rhs->length() != length) { + *equal = false; + return true; + } + + *equal = true; + RootedString k1(cx), k2(cx); + RootedId id(cx); + RootedValue v1(cx), v2(cx); + + Rooted sortedKeysLHS( + cx, &lhs->getFixedSlot(SORTED_KEYS_SLOT).toObject().as()); + Rooted sortedKeysRHS( + cx, &rhs->getFixedSlot(SORTED_KEYS_SLOT).toObject().as()); + + for (uint32_t index = 0; index < length; index++) { + k1.set(sortedKeysLHS->getDenseElement(index).toString()); + k2.set(sortedKeysRHS->getDenseElement(index).toString()); + + if (!EqualStrings(cx, k1, k2, equal)) { + return false; + } + if (!*equal) { + return true; + } + + if (!JS_StringToId(cx, k1, &id)) { + return false; + } + + // We already know that this is an own property of both records, so both + // calls must return true. + MOZ_ALWAYS_TRUE(lhs->getOwnProperty(cx, id, &v1)); + MOZ_ALWAYS_TRUE(rhs->getOwnProperty(cx, id, &v2)); + + if (!Comparator(cx, v1, v2, equal)) { + return false; + } + if (!*equal) { + return true; + } + } + + return true; +} + +// Record and Record proposal section 9.2.1 +static bool RecordConstructor(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (args.isConstructing()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_NOT_CONSTRUCTOR, "Record"); + return false; + } + // Step 2. + RootedObject obj(cx, ToObject(cx, args.get(0))); + if (!obj) { + return false; + } + + // Step 3. + RootedIdVector keys(cx); + if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, &keys)) { + return false; + } + + size_t len = keys.length(); + + Rooted rec(cx, RecordType::createUninitialized(cx, len)); + + if (!rec) { + return false; + } + + RootedId propKey(cx); + RootedValue propValue(cx); + for (size_t i = 0; i < len; i++) { + propKey.set(keys[i]); + MOZ_ASSERT(!propKey.isSymbol(), "symbols are filtered out at step 3"); + + // Step 4.c.ii.1. + if (MOZ_UNLIKELY(!GetProperty(cx, obj, obj, propKey, &propValue))) { + return false; + } + + if (MOZ_UNLIKELY(!rec->initializeNextProperty(cx, propKey, propValue))) { + return false; + } + } + + if (MOZ_UNLIKELY(!rec->finishInitialization(cx))) { + return false; + } + + args.rval().setExtendedPrimitive(*rec); + return true; +} + +JSString* js::RecordToSource(JSContext* cx, RecordType* rec) { + JSStringBuilder sb(cx); + + if (!sb.append("#{")) { + return nullptr; + } + + ArrayObject& sortedKeys = rec->getFixedSlot(RecordType::SORTED_KEYS_SLOT) + .toObject() + .as(); + + uint32_t length = sortedKeys.length(); + + Rooted rootedRec(cx, rec); + RootedValue value(cx); + RootedString keyStr(cx); + RootedId key(cx); + JSString* str; + for (uint32_t index = 0; index < length; index++) { + value.set(sortedKeys.getDenseElement(index)); + MOZ_ASSERT(value.isString()); + + str = ValueToSource(cx, value); + if (!str) { + return nullptr; + } + if (!sb.append(str)) { + return nullptr; + } + + if (!sb.append(": ")) { + return nullptr; + } + + keyStr.set(value.toString()); + if (!JS_StringToId(cx, keyStr, &key)) { + return nullptr; + } + + MOZ_ALWAYS_TRUE(rootedRec->getOwnProperty(cx, key, &value)); + + str = ValueToSource(cx, value); + if (!str) { + return nullptr; + } + 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(); +} -- cgit v1.2.3