summaryrefslogtreecommitdiffstats
path: root/js/src/vm/ObjectGroup.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/vm/ObjectGroup.cpp
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/vm/ObjectGroup.cpp')
-rw-r--r--js/src/vm/ObjectGroup.cpp395
1 files changed, 395 insertions, 0 deletions
diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp
new file mode 100644
index 0000000000..0887554869
--- /dev/null
+++ b/js/src/vm/ObjectGroup.cpp
@@ -0,0 +1,395 @@
+/* -*- 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/ObjectGroup.h"
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Unused.h"
+
+#include "jsexn.h"
+
+#include "builtin/DataViewObject.h"
+#include "gc/FreeOp.h"
+#include "gc/HashUtil.h"
+#include "gc/Policy.h"
+#include "gc/StoreBuffer.h"
+#include "js/CharacterEncoding.h"
+#include "js/friend/WindowProxy.h" // js::IsWindow
+#include "js/UniquePtr.h"
+#include "vm/ArrayObject.h"
+#include "vm/ErrorObject.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSObject.h"
+#include "vm/PlainObject.h" // js::PlainObject
+#include "vm/RegExpObject.h"
+#include "vm/Shape.h"
+#include "vm/TaggedProto.h"
+
+#include "gc/Marking-inl.h"
+#include "gc/ObjectKind-inl.h"
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+/////////////////////////////////////////////////////////////////////
+// ObjectGroup
+/////////////////////////////////////////////////////////////////////
+
+static ObjectGroup* MakeGroup(JSContext* cx, const JSClass* clasp,
+ Handle<TaggedProto> proto,
+ HandleTypeDescr descr) {
+ MOZ_ASSERT_IF(proto.isObject(),
+ cx->isInsideCurrentCompartment(proto.toObject()));
+
+ ObjectGroup* group = Allocate<ObjectGroup>(cx);
+ if (!group) {
+ return nullptr;
+ }
+ new (group) ObjectGroup(clasp, proto, cx->realm(), descr);
+
+ return group;
+}
+
+ObjectGroup::ObjectGroup(const JSClass* clasp, TaggedProto proto,
+ JS::Realm* realm, TypeDescr* descr)
+ : TenuredCellWithNonGCPointer(clasp),
+ proto_(proto),
+ realm_(realm),
+ typeDescr_(descr) {
+ /* Windows may not appear on prototype chains. */
+ MOZ_ASSERT_IF(proto.isObject(), !IsWindow(proto.toObject()));
+ MOZ_ASSERT(JS::StringIsASCII(clasp->name));
+
+#ifdef DEBUG
+ GlobalObject* global = realm->unsafeUnbarrieredMaybeGlobal();
+ if (global) {
+ AssertTargetIsNotGray(global);
+ }
+#endif
+}
+
+void ObjectGroup::setProtoUnchecked(TaggedProto proto) {
+ proto_ = proto;
+ MOZ_ASSERT_IF(proto_.isObject() && proto_.toObject()->isNative(),
+ proto_.toObject()->isDelegate());
+}
+
+/////////////////////////////////////////////////////////////////////
+// GlobalObject
+/////////////////////////////////////////////////////////////////////
+
+bool GlobalObject::shouldSplicePrototype() {
+ // During bootstrapping, we need to make sure not to splice a new prototype in
+ // for the global object if its __proto__ had previously been set to non-null,
+ // as this will change the prototype for all other objects with the same type.
+ return staticPrototype() == nullptr;
+}
+
+/* static */
+bool GlobalObject::splicePrototype(JSContext* cx, Handle<GlobalObject*> global,
+ Handle<TaggedProto> proto) {
+ MOZ_ASSERT(cx->realm() == global->realm());
+
+ // Windows may not appear on prototype chains.
+ MOZ_ASSERT_IF(proto.isObject(), !IsWindow(proto.toObject()));
+
+ if (proto.isObject()) {
+ RootedObject protoObj(cx, proto.toObject());
+ if (!JSObject::setDelegate(cx, protoObj)) {
+ return false;
+ }
+ }
+
+ ObjectGroup* group = MakeGroup(cx, global->getClass(), proto,
+ /* descr = */ nullptr);
+ if (!group) {
+ return false;
+ }
+
+ global->setGroupRaw(group);
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////
+// ObjectGroupRealm NewTable
+/////////////////////////////////////////////////////////////////////
+
+/*
+ * Entries for the per-realm set of groups based on prototype and class. An
+ * optional associated object is used which allows multiple groups to be
+ * created with the same prototype. The associated object is a type descriptor
+ * (for typed objects).
+ */
+struct ObjectGroupRealm::NewEntry {
+ WeakHeapPtrObjectGroup group;
+
+ // Note: This pointer is only used for equality and does not need a read
+ // barrier.
+ TypeDescr* associated;
+
+ NewEntry(ObjectGroup* group, TypeDescr* associated)
+ : group(group), associated(associated) {}
+
+ struct Lookup {
+ const JSClass* clasp;
+ TaggedProto proto;
+ TypeDescr* associated;
+
+ Lookup(const JSClass* clasp, TaggedProto proto, TypeDescr* associated)
+ : clasp(clasp), proto(proto), associated(associated) {
+ MOZ_ASSERT(clasp);
+ }
+
+ explicit Lookup(const NewEntry& entry)
+ : clasp(entry.group.unbarrieredGet()->clasp()),
+ proto(entry.group.unbarrieredGet()->proto()),
+ associated(entry.associated) {}
+ };
+
+ bool needsSweep() {
+ return IsAboutToBeFinalized(&group) ||
+ (associated && IsAboutToBeFinalizedUnbarriered(&associated));
+ }
+
+ bool operator==(const NewEntry& other) const {
+ return group == other.group && associated == other.associated;
+ }
+};
+
+namespace js {
+template <>
+struct MovableCellHasher<ObjectGroupRealm::NewEntry> {
+ using Key = ObjectGroupRealm::NewEntry;
+ using Lookup = ObjectGroupRealm::NewEntry::Lookup;
+
+ static bool hasHash(const Lookup& l) {
+ return MovableCellHasher<TaggedProto>::hasHash(l.proto) &&
+ MovableCellHasher<JSObject*>::hasHash(l.associated);
+ }
+
+ static bool ensureHash(const Lookup& l) {
+ return MovableCellHasher<TaggedProto>::ensureHash(l.proto) &&
+ MovableCellHasher<JSObject*>::ensureHash(l.associated);
+ }
+
+ static inline HashNumber hash(const Lookup& lookup) {
+ HashNumber hash = MovableCellHasher<TaggedProto>::hash(lookup.proto);
+ hash = mozilla::AddToHash(
+ hash, MovableCellHasher<JSObject*>::hash(lookup.associated));
+ return mozilla::AddToHash(hash, mozilla::HashGeneric(lookup.clasp));
+ }
+
+ static inline bool match(const ObjectGroupRealm::NewEntry& key,
+ const Lookup& lookup) {
+ if (key.group.unbarrieredGet()->clasp() != lookup.clasp) {
+ return false;
+ }
+
+ TaggedProto proto = key.group.unbarrieredGet()->proto();
+ if (!MovableCellHasher<TaggedProto>::match(proto, lookup.proto)) {
+ return false;
+ }
+
+ return MovableCellHasher<JSObject*>::match(key.associated,
+ lookup.associated);
+ }
+};
+} // namespace js
+
+class ObjectGroupRealm::NewTable
+ : public JS::WeakCache<js::GCHashSet<NewEntry, MovableCellHasher<NewEntry>,
+ SystemAllocPolicy>> {
+ using Table =
+ js::GCHashSet<NewEntry, MovableCellHasher<NewEntry>, SystemAllocPolicy>;
+ using Base = JS::WeakCache<Table>;
+
+ public:
+ explicit NewTable(Zone* zone) : Base(zone) {}
+};
+
+/* static*/ ObjectGroupRealm& ObjectGroupRealm::getForNewObject(JSContext* cx) {
+ return cx->realm()->objectGroups_;
+}
+
+MOZ_ALWAYS_INLINE ObjectGroup* ObjectGroupRealm::DefaultNewGroupCache::lookup(
+ const JSClass* clasp, TaggedProto proto, TypeDescr* associated) {
+ if (group_ && associated_ == associated && group_->proto() == proto &&
+ group_->clasp() == clasp) {
+ return group_;
+ }
+ return nullptr;
+}
+
+/* static */
+ObjectGroup* ObjectGroup::defaultNewGroup(JSContext* cx, const JSClass* clasp,
+ TaggedProto proto,
+ Handle<TypeDescr*> descr) {
+ MOZ_ASSERT(clasp);
+ MOZ_ASSERT_IF(descr, proto.isObject());
+ MOZ_ASSERT_IF(proto.isObject(),
+ cx->isInsideCurrentCompartment(proto.toObject()));
+
+ ObjectGroupRealm& groups = ObjectGroupRealm::getForNewObject(cx);
+
+ if (ObjectGroup* group =
+ groups.defaultNewGroupCache.lookup(clasp, proto, descr)) {
+ return group;
+ }
+
+ gc::AutoSuppressGC suppressGC(cx);
+
+ ObjectGroupRealm::NewTable*& table = groups.defaultNewTable;
+
+ if (!table) {
+ table = cx->new_<ObjectGroupRealm::NewTable>(cx->zone());
+ if (!table) {
+ return nullptr;
+ }
+ }
+
+ if (proto.isObject() && !proto.toObject()->isDelegate()) {
+ RootedObject protoObj(cx, proto.toObject());
+ if (!JSObject::setDelegate(cx, protoObj)) {
+ return nullptr;
+ }
+ }
+
+ ObjectGroupRealm::NewTable::AddPtr p = table->lookupForAdd(
+ ObjectGroupRealm::NewEntry::Lookup(clasp, proto, descr));
+ if (p) {
+ ObjectGroup* group = p->group;
+ MOZ_ASSERT(group->clasp() == clasp);
+ MOZ_ASSERT(group->proto() == proto);
+ groups.defaultNewGroupCache.put(group, descr);
+ return group;
+ }
+
+ Rooted<TaggedProto> protoRoot(cx, proto);
+ ObjectGroup* group = MakeGroup(cx, clasp, protoRoot, descr);
+ if (!group) {
+ return nullptr;
+ }
+
+ if (!table->add(p, ObjectGroupRealm::NewEntry(group, descr))) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ groups.defaultNewGroupCache.put(group, descr);
+ return group;
+}
+
+static bool AddPlainObjectProperties(JSContext* cx, HandlePlainObject obj,
+ IdValuePair* properties,
+ size_t nproperties) {
+ RootedId propid(cx);
+ RootedValue value(cx);
+
+ for (size_t i = 0; i < nproperties; i++) {
+ propid = properties[i].id;
+ value = properties[i].value;
+ if (!NativeDefineDataProperty(cx, obj, propid, value, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+PlainObject* js::NewPlainObjectWithProperties(JSContext* cx,
+ IdValuePair* properties,
+ size_t nproperties,
+ NewObjectKind newKind) {
+ gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties);
+ RootedPlainObject obj(
+ cx, NewBuiltinClassInstance<PlainObject>(cx, allocKind, newKind));
+ if (!obj || !AddPlainObjectProperties(cx, obj, properties, nproperties)) {
+ return nullptr;
+ }
+ return obj;
+}
+
+/////////////////////////////////////////////////////////////////////
+// ObjectGroupRealm
+/////////////////////////////////////////////////////////////////////
+
+ObjectGroupRealm::~ObjectGroupRealm() { js_delete(defaultNewTable); }
+
+void ObjectGroupRealm::addSizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf, size_t* realmTables) {
+ if (defaultNewTable) {
+ *realmTables += defaultNewTable->sizeOfIncludingThis(mallocSizeOf);
+ }
+}
+
+void ObjectGroupRealm::clearTables() {
+ if (defaultNewTable) {
+ defaultNewTable->clear();
+ }
+ defaultNewGroupCache.purge();
+}
+
+void ObjectGroupRealm::fixupNewTableAfterMovingGC(NewTable* table) {
+ /*
+ * Each entry's hash depends on the object's prototype and we can't tell
+ * whether that has been moved or not in sweepNewObjectGroupTable().
+ */
+ if (table) {
+ for (NewTable::Enum e(*table); !e.empty(); e.popFront()) {
+ NewEntry& entry = e.mutableFront();
+
+ ObjectGroup* group = entry.group.unbarrieredGet();
+ if (IsForwarded(group)) {
+ group = Forwarded(group);
+ entry.group.set(group);
+ }
+ TaggedProto proto = group->proto();
+ if (proto.isObject() && IsForwarded(proto.toObject())) {
+ proto = TaggedProto(Forwarded(proto.toObject()));
+ // Update the group's proto here so that we are able to lookup
+ // entries in this table before all object pointers are updated.
+ group->proto() = proto;
+ }
+ if (entry.associated && IsForwarded(entry.associated)) {
+ entry.associated = Forwarded(entry.associated);
+ }
+ }
+ }
+}
+
+#ifdef JSGC_HASH_TABLE_CHECKS
+
+void ObjectGroupRealm::checkNewTableAfterMovingGC(NewTable* table) {
+ /*
+ * Assert that nothing points into the nursery or needs to be relocated, and
+ * that the hash table entries are discoverable.
+ */
+ if (!table) {
+ return;
+ }
+
+ for (auto r = table->all(); !r.empty(); r.popFront()) {
+ NewEntry entry = r.front();
+ CheckGCThingAfterMovingGC(entry.group.unbarrieredGet());
+ TaggedProto proto = entry.group.unbarrieredGet()->proto();
+ if (proto.isObject()) {
+ CheckGCThingAfterMovingGC(proto.toObject());
+ }
+ CheckGCThingAfterMovingGC(entry.associated);
+
+ auto ptr = table->lookup(NewEntry::Lookup(entry));
+ MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front());
+ }
+}
+
+#endif // JSGC_HASH_TABLE_CHECKS
+
+JS::ubi::Node::Size JS::ubi::Concrete<js::ObjectGroup>::size(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind());
+ return size;
+}