1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * JS object implementation.
11 #include "vm/PlainObject-inl.h"
13 #include "mozilla/Assertions.h" // MOZ_ASSERT
15 #include "jspubtd.h" // JSProto_Object
17 #include "ds/IdValuePair.h" // js::IdValuePair
18 #include "gc/AllocKind.h" // js::gc::AllocKind
19 #include "vm/JSContext.h" // JSContext
20 #include "vm/JSFunction.h" // JSFunction
21 #include "vm/JSObject.h" // JSObject, js::GetPrototypeFromConstructor
22 #include "vm/TaggedProto.h" // js::TaggedProto
24 #include "vm/JSFunction-inl.h"
25 #include "vm/JSObject-inl.h" // js::NewObjectWithGroup, js::NewObjectGCKind
32 static MOZ_ALWAYS_INLINE SharedShape
* GetPlainObjectShapeWithProto(
33 JSContext
* cx
, JSObject
* proto
, gc::AllocKind kind
) {
34 MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(&PlainObject::class_
) == 0,
35 "all slots can be used for properties");
36 uint32_t nfixed
= GetGCKindSlots(kind
);
37 return SharedShape::getInitialShape(cx
, &PlainObject::class_
, cx
->realm(),
38 TaggedProto(proto
), nfixed
);
41 SharedShape
* js::ThisShapeForFunction(JSContext
* cx
, Handle
<JSFunction
*> callee
,
42 Handle
<JSObject
*> newTarget
) {
43 MOZ_ASSERT(cx
->realm() == callee
->realm());
44 MOZ_ASSERT(!callee
->constructorNeedsUninitializedThis());
46 Rooted
<JSObject
*> proto(cx
);
47 if (!GetPrototypeFromConstructor(cx
, newTarget
, JSProto_Object
, &proto
)) {
51 js::gc::AllocKind allocKind
= NewObjectGCKind();
52 if (!JSFunction::getAllocKindForThis(cx
, callee
, allocKind
)) {
57 if (proto
&& proto
!= cx
->global()->maybeGetPrototype(JSProto_Object
)) {
58 res
= GetPlainObjectShapeWithProto(cx
, proto
, allocKind
);
60 res
= GlobalObject::getPlainObjectShapeWithDefaultProto(cx
, allocKind
);
63 MOZ_ASSERT_IF(res
, res
->realm() == callee
->realm());
69 void PlainObject::assertHasNoNonWritableOrAccessorPropExclProto() const {
70 // Check the most recent MaxCount properties to not slow down debug builds too
72 static constexpr size_t MaxCount
= 8;
75 PropertyName
* protoName
= runtimeFromMainThread()->commonNames
->proto_
;
77 for (ShapePropertyIter
<NoGC
> iter(shape()); !iter
.done(); iter
++) {
78 // __proto__ is always allowed.
79 if (iter
->key().isAtom(protoName
)) {
83 MOZ_ASSERT(iter
->isDataProperty());
84 MOZ_ASSERT(iter
->writable());
87 if (count
> MaxCount
) {
95 PlainObject
* PlainObject::createWithTemplateFromDifferentRealm(
96 JSContext
* cx
, Handle
<PlainObject
*> templateObject
) {
97 MOZ_ASSERT(cx
->realm() != templateObject
->realm(),
98 "Use createWithTemplate() for same-realm objects");
100 // Currently only implemented for null-proto.
101 MOZ_ASSERT(templateObject
->staticPrototype() == nullptr);
103 // The object mustn't be in dictionary mode.
104 MOZ_ASSERT(!templateObject
->shape()->isDictionary());
106 TaggedProto proto
= TaggedProto(nullptr);
107 SharedShape
* templateShape
= templateObject
->sharedShape();
108 Rooted
<SharedPropMap
*> map(cx
, templateShape
->propMap());
110 Rooted
<SharedShape
*> shape(
111 cx
, SharedShape::getInitialOrPropMapShape(
112 cx
, &PlainObject::class_
, cx
->realm(), proto
,
113 templateShape
->numFixedSlots(), map
,
114 templateShape
->propMapLength(), templateShape
->objectFlags()));
118 return createWithShape(cx
, shape
);
122 SharedShape
* GlobalObject::createPlainObjectShapeWithDefaultProto(
123 JSContext
* cx
, gc::AllocKind kind
) {
124 PlainObjectSlotsKind slotsKind
= PlainObjectSlotsKindFromAllocKind(kind
);
125 HeapPtr
<SharedShape
*>& shapeRef
=
126 cx
->global()->data().plainObjectShapesWithDefaultProto
[slotsKind
];
127 MOZ_ASSERT(!shapeRef
);
129 JSObject
* proto
= &cx
->global()->getObjectPrototype();
130 SharedShape
* shape
= GetPlainObjectShapeWithProto(cx
, proto
, kind
);
135 shapeRef
.init(shape
);
139 PlainObject
* js::NewPlainObject(JSContext
* cx
, NewObjectKind newKind
) {
140 constexpr gc::AllocKind allocKind
= gc::AllocKind::OBJECT0
;
141 MOZ_ASSERT(gc::GetGCObjectKind(&PlainObject::class_
) == allocKind
);
143 Rooted
<SharedShape
*> shape(
144 cx
, GlobalObject::getPlainObjectShapeWithDefaultProto(cx
, allocKind
));
149 return PlainObject::createWithShape(cx
, shape
, allocKind
, newKind
);
152 PlainObject
* js::NewPlainObjectWithAllocKind(JSContext
* cx
,
153 gc::AllocKind allocKind
,
154 NewObjectKind newKind
) {
155 Rooted
<SharedShape
*> shape(
156 cx
, GlobalObject::getPlainObjectShapeWithDefaultProto(cx
, allocKind
));
161 return PlainObject::createWithShape(cx
, shape
, allocKind
, newKind
);
164 PlainObject
* js::NewPlainObjectWithProto(JSContext
* cx
, HandleObject proto
,
165 NewObjectKind newKind
) {
166 // Use a faster path if |proto| is %Object.prototype% (the common case).
167 if (proto
&& proto
== cx
->global()->maybeGetPrototype(JSProto_Object
)) {
168 return NewPlainObject(cx
, newKind
);
171 constexpr gc::AllocKind allocKind
= gc::AllocKind::OBJECT0
;
172 MOZ_ASSERT(gc::GetGCObjectKind(&PlainObject::class_
) == allocKind
);
174 Rooted
<SharedShape
*> shape(
175 cx
, GetPlainObjectShapeWithProto(cx
, proto
, allocKind
));
180 return PlainObject::createWithShape(cx
, shape
, allocKind
, newKind
);
183 PlainObject
* js::NewPlainObjectWithProtoAndAllocKind(JSContext
* cx
,
185 gc::AllocKind allocKind
,
186 NewObjectKind newKind
) {
187 // Use a faster path if |proto| is %Object.prototype% (the common case).
188 if (proto
&& proto
== cx
->global()->maybeGetPrototype(JSProto_Object
)) {
189 return NewPlainObjectWithAllocKind(cx
, allocKind
, newKind
);
192 Rooted
<SharedShape
*> shape(
193 cx
, GetPlainObjectShapeWithProto(cx
, proto
, allocKind
));
198 return PlainObject::createWithShape(cx
, shape
, allocKind
, newKind
);
201 void js::NewPlainObjectWithPropsCache::add(SharedShape
* shape
) {
203 MOZ_ASSERT(shape
->slotSpan() > 0);
204 for (size_t i
= NumEntries
- 1; i
> 0; i
--) {
205 entries_
[i
] = entries_
[i
- 1];
210 static bool ShapeMatches(IdValuePair
* properties
, size_t nproperties
,
211 SharedShape
* shape
) {
212 if (shape
->slotSpan() != nproperties
) {
215 SharedShapePropertyIter
<NoGC
> iter(shape
);
216 for (size_t i
= nproperties
; i
> 0; i
--) {
217 MOZ_ASSERT(iter
->isDataProperty());
218 MOZ_ASSERT(iter
->flags() == PropertyFlags::defaultDataPropFlags
);
219 if (properties
[i
- 1].id
!= iter
->key()) {
224 MOZ_ASSERT(iter
.done());
228 SharedShape
* js::NewPlainObjectWithPropsCache::lookup(
229 IdValuePair
* properties
, size_t nproperties
) const {
230 for (size_t i
= 0; i
< NumEntries
; i
++) {
231 SharedShape
* shape
= entries_
[i
];
232 if (shape
&& ShapeMatches(properties
, nproperties
, shape
)) {
239 enum class KeysKind
{ UniqueNames
, Unknown
};
241 template <KeysKind Kind
>
242 static PlainObject
* NewPlainObjectWithProperties(JSContext
* cx
,
243 IdValuePair
* properties
,
245 NewObjectKind newKind
) {
246 auto& cache
= cx
->realm()->newPlainObjectWithPropsCache
;
248 // If we recently created an object with these properties, we can use that
250 if (SharedShape
* shape
= cache
.lookup(properties
, nproperties
)) {
251 Rooted
<SharedShape
*> shapeRoot(cx
, shape
);
252 PlainObject
* obj
= PlainObject::createWithShape(cx
, shapeRoot
, newKind
);
256 MOZ_ASSERT(obj
->slotSpan() == nproperties
);
257 for (size_t i
= 0; i
< nproperties
; i
++) {
258 obj
->initSlot(i
, properties
[i
].value
);
263 gc::AllocKind allocKind
= gc::GetGCObjectKind(nproperties
);
264 Rooted
<PlainObject
*> obj(cx
,
265 NewPlainObjectWithAllocKind(cx
, allocKind
, newKind
));
270 if (nproperties
== 0) {
274 Rooted
<PropertyKey
> key(cx
);
275 Rooted
<Value
> value(cx
);
276 bool canCache
= true;
278 for (size_t i
= 0; i
< nproperties
; i
++) {
279 key
= properties
[i
].id
;
280 value
= properties
[i
].value
;
282 // Integer keys may need to be stored in dense elements. This is uncommon so
283 // just fall back to NativeDefineDataProperty.
284 if constexpr (Kind
== KeysKind::Unknown
) {
285 if (MOZ_UNLIKELY(key
.isInt())) {
287 if (!NativeDefineDataProperty(cx
, obj
, key
, value
, JSPROP_ENUMERATE
)) {
294 MOZ_ASSERT(key
.isAtom() || key
.isSymbol());
296 // Check for duplicate keys. In this case we must overwrite the earlier
298 if constexpr (Kind
== KeysKind::UniqueNames
) {
299 MOZ_ASSERT(!obj
->containsPure(key
));
301 mozilla::Maybe
<PropertyInfo
> prop
= obj
->lookup(cx
, key
);
302 if (MOZ_UNLIKELY(prop
)) {
304 MOZ_ASSERT(prop
->isDataProperty());
305 obj
->setSlot(prop
->slot(), value
);
310 if (!AddDataPropertyToPlainObject(cx
, obj
, key
, value
)) {
315 if (canCache
&& !obj
->inDictionaryMode()) {
316 MOZ_ASSERT(obj
->getDenseInitializedLength() == 0);
317 MOZ_ASSERT(obj
->slotSpan() == nproperties
);
318 cache
.add(obj
->sharedShape());
324 PlainObject
* js::NewPlainObjectWithUniqueNames(JSContext
* cx
,
325 IdValuePair
* properties
,
327 NewObjectKind newKind
) {
328 return NewPlainObjectWithProperties
<KeysKind::UniqueNames
>(
329 cx
, properties
, nproperties
, newKind
);
332 PlainObject
* js::NewPlainObjectWithMaybeDuplicateKeys(JSContext
* cx
,
333 IdValuePair
* properties
,
335 NewObjectKind newKind
) {
336 return NewPlainObjectWithProperties
<KeysKind::Unknown
>(cx
, properties
,
337 nproperties
, newKind
);