summaryrefslogtreecommitdiffstats
path: root/js/src/vm/PlainObject.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/vm/PlainObject.cpp330
1 files changed, 330 insertions, 0 deletions
diff --git a/js/src/vm/PlainObject.cpp b/js/src/vm/PlainObject.cpp
new file mode 100644
index 0000000000..198c65594b
--- /dev/null
+++ b/js/src/vm/PlainObject.cpp
@@ -0,0 +1,330 @@
+/* -*- 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/. */
+
+/*
+ * JS object implementation.
+ */
+
+#include "vm/PlainObject-inl.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include "jspubtd.h" // JSProto_Object
+
+#include "ds/IdValuePair.h" // js::IdValuePair
+#include "gc/AllocKind.h" // js::gc::AllocKind
+#include "vm/JSContext.h" // JSContext
+#include "vm/JSFunction.h" // JSFunction
+#include "vm/JSObject.h" // JSObject, js::GetPrototypeFromConstructor
+#include "vm/TaggedProto.h" // js::TaggedProto
+
+#include "vm/JSObject-inl.h" // js::NewObjectWithGroup, js::NewObjectGCKind
+
+using namespace js;
+
+using JS::Handle;
+using JS::Rooted;
+
+static MOZ_ALWAYS_INLINE SharedShape* GetPlainObjectShapeWithProto(
+ JSContext* cx, JSObject* proto, gc::AllocKind kind) {
+ MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(&PlainObject::class_) == 0,
+ "all slots can be used for properties");
+ uint32_t nfixed = GetGCKindSlots(kind);
+ return SharedShape::getInitialShape(cx, &PlainObject::class_, cx->realm(),
+ TaggedProto(proto), nfixed);
+}
+
+SharedShape* js::ThisShapeForFunction(JSContext* cx, Handle<JSFunction*> callee,
+ Handle<JSObject*> newTarget) {
+ MOZ_ASSERT(cx->realm() == callee->realm());
+ MOZ_ASSERT(!callee->constructorNeedsUninitializedThis());
+
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromConstructor(cx, newTarget, JSProto_Object, &proto)) {
+ return nullptr;
+ }
+
+ js::gc::AllocKind allocKind = NewObjectGCKind();
+
+ SharedShape* res;
+ if (proto && proto != cx->global()->maybeGetPrototype(JSProto_Object)) {
+ res = GetPlainObjectShapeWithProto(cx, proto, allocKind);
+ } else {
+ res = GlobalObject::getPlainObjectShapeWithDefaultProto(cx, allocKind);
+ }
+
+ MOZ_ASSERT_IF(res, res->realm() == callee->realm());
+
+ return res;
+}
+
+#ifdef DEBUG
+void PlainObject::assertHasNoNonWritableOrAccessorPropExclProto() const {
+ // Check the most recent MaxCount properties to not slow down debug builds too
+ // much.
+ static constexpr size_t MaxCount = 8;
+
+ size_t count = 0;
+ PropertyName* protoName = runtimeFromMainThread()->commonNames->proto;
+
+ for (ShapePropertyIter<NoGC> iter(shape()); !iter.done(); iter++) {
+ // __proto__ is always allowed.
+ if (iter->key().isAtom(protoName)) {
+ continue;
+ }
+
+ MOZ_ASSERT(iter->isDataProperty());
+ MOZ_ASSERT(iter->writable());
+
+ count++;
+ if (count > MaxCount) {
+ return;
+ }
+ }
+}
+#endif
+
+// static
+PlainObject* PlainObject::createWithTemplateFromDifferentRealm(
+ JSContext* cx, Handle<PlainObject*> templateObject) {
+ MOZ_ASSERT(cx->realm() != templateObject->realm(),
+ "Use createWithTemplate() for same-realm objects");
+
+ // Currently only implemented for null-proto.
+ MOZ_ASSERT(templateObject->staticPrototype() == nullptr);
+
+ // The object mustn't be in dictionary mode.
+ MOZ_ASSERT(!templateObject->shape()->isDictionary());
+
+ TaggedProto proto = TaggedProto(nullptr);
+ SharedShape* templateShape = templateObject->sharedShape();
+ Rooted<SharedPropMap*> map(cx, templateShape->propMap());
+
+ Rooted<SharedShape*> shape(
+ cx, SharedShape::getInitialOrPropMapShape(
+ cx, &PlainObject::class_, cx->realm(), proto,
+ templateShape->numFixedSlots(), map,
+ templateShape->propMapLength(), templateShape->objectFlags()));
+ if (!shape) {
+ return nullptr;
+ }
+ return createWithShape(cx, shape);
+}
+
+// static
+SharedShape* GlobalObject::createPlainObjectShapeWithDefaultProto(
+ JSContext* cx, gc::AllocKind kind) {
+ PlainObjectSlotsKind slotsKind = PlainObjectSlotsKindFromAllocKind(kind);
+ HeapPtr<SharedShape*>& shapeRef =
+ cx->global()->data().plainObjectShapesWithDefaultProto[slotsKind];
+ MOZ_ASSERT(!shapeRef);
+
+ JSObject* proto = &cx->global()->getObjectPrototype();
+ SharedShape* shape = GetPlainObjectShapeWithProto(cx, proto, kind);
+ if (!shape) {
+ return nullptr;
+ }
+
+ shapeRef.init(shape);
+ return shape;
+}
+
+PlainObject* js::NewPlainObject(JSContext* cx, NewObjectKind newKind) {
+ constexpr gc::AllocKind allocKind = gc::AllocKind::OBJECT0;
+ MOZ_ASSERT(gc::GetGCObjectKind(&PlainObject::class_) == allocKind);
+
+ Rooted<SharedShape*> shape(
+ cx, GlobalObject::getPlainObjectShapeWithDefaultProto(cx, allocKind));
+ if (!shape) {
+ return nullptr;
+ }
+
+ return PlainObject::createWithShape(cx, shape, allocKind, newKind);
+}
+
+PlainObject* js::NewPlainObjectWithAllocKind(JSContext* cx,
+ gc::AllocKind allocKind,
+ NewObjectKind newKind) {
+ Rooted<SharedShape*> shape(
+ cx, GlobalObject::getPlainObjectShapeWithDefaultProto(cx, allocKind));
+ if (!shape) {
+ return nullptr;
+ }
+
+ return PlainObject::createWithShape(cx, shape, allocKind, newKind);
+}
+
+PlainObject* js::NewPlainObjectWithProto(JSContext* cx, HandleObject proto,
+ NewObjectKind newKind) {
+ // Use a faster path if |proto| is %Object.prototype% (the common case).
+ if (proto && proto == cx->global()->maybeGetPrototype(JSProto_Object)) {
+ return NewPlainObject(cx, newKind);
+ }
+
+ constexpr gc::AllocKind allocKind = gc::AllocKind::OBJECT0;
+ MOZ_ASSERT(gc::GetGCObjectKind(&PlainObject::class_) == allocKind);
+
+ Rooted<SharedShape*> shape(
+ cx, GetPlainObjectShapeWithProto(cx, proto, allocKind));
+ if (!shape) {
+ return nullptr;
+ }
+
+ return PlainObject::createWithShape(cx, shape, allocKind, newKind);
+}
+
+PlainObject* js::NewPlainObjectWithProtoAndAllocKind(JSContext* cx,
+ HandleObject proto,
+ gc::AllocKind allocKind,
+ NewObjectKind newKind) {
+ // Use a faster path if |proto| is %Object.prototype% (the common case).
+ if (proto && proto == cx->global()->maybeGetPrototype(JSProto_Object)) {
+ return NewPlainObjectWithAllocKind(cx, allocKind, newKind);
+ }
+
+ Rooted<SharedShape*> shape(
+ cx, GetPlainObjectShapeWithProto(cx, proto, allocKind));
+ if (!shape) {
+ return nullptr;
+ }
+
+ return PlainObject::createWithShape(cx, shape, allocKind, newKind);
+}
+
+void js::NewPlainObjectWithPropsCache::add(SharedShape* shape) {
+ MOZ_ASSERT(shape);
+ MOZ_ASSERT(shape->slotSpan() > 0);
+ for (size_t i = NumEntries - 1; i > 0; i--) {
+ entries_[i] = entries_[i - 1];
+ }
+ entries_[0] = shape;
+}
+
+static bool ShapeMatches(IdValuePair* properties, size_t nproperties,
+ SharedShape* shape) {
+ if (shape->slotSpan() != nproperties) {
+ return false;
+ }
+ SharedShapePropertyIter<NoGC> iter(shape);
+ for (size_t i = nproperties; i > 0; i--) {
+ MOZ_ASSERT(iter->isDataProperty());
+ MOZ_ASSERT(iter->flags() == PropertyFlags::defaultDataPropFlags);
+ if (properties[i - 1].id != iter->key()) {
+ return false;
+ }
+ iter++;
+ }
+ MOZ_ASSERT(iter.done());
+ return true;
+}
+
+SharedShape* js::NewPlainObjectWithPropsCache::lookup(
+ IdValuePair* properties, size_t nproperties) const {
+ for (size_t i = 0; i < NumEntries; i++) {
+ SharedShape* shape = entries_[i];
+ if (shape && ShapeMatches(properties, nproperties, shape)) {
+ return shape;
+ }
+ }
+ return nullptr;
+}
+
+enum class KeysKind { UniqueNames, Unknown };
+
+template <KeysKind Kind>
+static PlainObject* NewPlainObjectWithProperties(JSContext* cx,
+ IdValuePair* properties,
+ size_t nproperties) {
+ auto& cache = cx->realm()->newPlainObjectWithPropsCache;
+
+ // If we recently created an object with these properties, we can use that
+ // Shape directly.
+ if (SharedShape* shape = cache.lookup(properties, nproperties)) {
+ Rooted<SharedShape*> shapeRoot(cx, shape);
+ PlainObject* obj = PlainObject::createWithShape(cx, shapeRoot);
+ if (!obj) {
+ return nullptr;
+ }
+ MOZ_ASSERT(obj->slotSpan() == nproperties);
+ for (size_t i = 0; i < nproperties; i++) {
+ obj->initSlot(i, properties[i].value);
+ }
+ return obj;
+ }
+
+ gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties);
+ Rooted<PlainObject*> obj(cx, NewPlainObjectWithAllocKind(cx, allocKind));
+ if (!obj) {
+ return nullptr;
+ }
+
+ if (nproperties == 0) {
+ return obj;
+ }
+
+ Rooted<PropertyKey> key(cx);
+ Rooted<Value> value(cx);
+ bool canCache = true;
+
+ for (size_t i = 0; i < nproperties; i++) {
+ key = properties[i].id;
+ value = properties[i].value;
+
+ // Integer keys may need to be stored in dense elements. This is uncommon so
+ // just fall back to NativeDefineDataProperty.
+ if constexpr (Kind == KeysKind::Unknown) {
+ if (MOZ_UNLIKELY(key.isInt())) {
+ canCache = false;
+ if (!NativeDefineDataProperty(cx, obj, key, value, JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ continue;
+ }
+ }
+
+ MOZ_ASSERT(key.isAtom() || key.isSymbol());
+
+ // Check for duplicate keys. In this case we must overwrite the earlier
+ // property value.
+ if constexpr (Kind == KeysKind::UniqueNames) {
+ MOZ_ASSERT(!obj->containsPure(key));
+ } else {
+ mozilla::Maybe<PropertyInfo> prop = obj->lookup(cx, key);
+ if (MOZ_UNLIKELY(prop)) {
+ canCache = false;
+ MOZ_ASSERT(prop->isDataProperty());
+ obj->setSlot(prop->slot(), value);
+ continue;
+ }
+ }
+
+ if (!AddDataPropertyToPlainObject(cx, obj, key, value)) {
+ return nullptr;
+ }
+ }
+
+ if (canCache && !obj->inDictionaryMode()) {
+ MOZ_ASSERT(obj->getDenseInitializedLength() == 0);
+ MOZ_ASSERT(obj->slotSpan() == nproperties);
+ cache.add(obj->sharedShape());
+ }
+
+ return obj;
+}
+
+PlainObject* js::NewPlainObjectWithUniqueNames(JSContext* cx,
+ IdValuePair* properties,
+ size_t nproperties) {
+ return NewPlainObjectWithProperties<KeysKind::UniqueNames>(cx, properties,
+ nproperties);
+}
+
+PlainObject* js::NewPlainObjectWithMaybeDuplicateKeys(JSContext* cx,
+ IdValuePair* properties,
+ size_t nproperties) {
+ return NewPlainObjectWithProperties<KeysKind::Unknown>(cx, properties,
+ nproperties);
+}