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/. */
6 
7 /*
8  * JS object implementation.
9  */
10 
11 #include "vm/PlainObject-inl.h"
12 
13 #include "mozilla/Assertions.h"  // MOZ_ASSERT
14 
15 #include "jspubtd.h"  // JSProto_Object
16 
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
23 
24 #include "vm/JSObject-inl.h"  // js::NewObjectWithGroup, js::NewObjectGCKind
25 
26 using namespace js;
27 
28 using JS::Handle;
29 using JS::Rooted;
30 
GetPlainObjectShapeWithProto(JSContext * cx,JSObject * proto,gc::AllocKind kind)31 static MOZ_ALWAYS_INLINE Shape* GetPlainObjectShapeWithProto(
32     JSContext* cx, JSObject* proto, gc::AllocKind kind) {
33   MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(&PlainObject::class_) == 0,
34              "all slots can be used for properties");
35   uint32_t nfixed = GetGCKindSlots(kind);
36   return SharedShape::getInitialShape(cx, &PlainObject::class_, cx->realm(),
37                                       TaggedProto(proto), nfixed);
38 }
39 
ThisShapeForFunction(JSContext * cx,Handle<JSFunction * > callee,Handle<JSObject * > newTarget)40 Shape* js::ThisShapeForFunction(JSContext* cx, Handle<JSFunction*> callee,
41                                 Handle<JSObject*> newTarget) {
42   MOZ_ASSERT(cx->realm() == callee->realm());
43   MOZ_ASSERT(!callee->constructorNeedsUninitializedThis());
44 
45   Rooted<JSObject*> proto(cx);
46   if (!GetPrototypeFromConstructor(cx, newTarget, JSProto_Object, &proto)) {
47     return nullptr;
48   }
49 
50   js::gc::AllocKind allocKind = NewObjectGCKind();
51 
52   Shape* res;
53   if (proto && proto != cx->global()->maybeGetPrototype(JSProto_Object)) {
54     res = GetPlainObjectShapeWithProto(cx, proto, allocKind);
55   } else {
56     res = GlobalObject::getPlainObjectShapeWithDefaultProto(cx, allocKind);
57   }
58 
59   MOZ_ASSERT_IF(res, res->realm() == callee->realm());
60 
61   return res;
62 }
63 
64 #ifdef DEBUG
assertHasNoNonWritableOrAccessorPropExclProto() const65 void PlainObject::assertHasNoNonWritableOrAccessorPropExclProto() const {
66   // Check the most recent MaxCount properties to not slow down debug builds too
67   // much.
68   static constexpr size_t MaxCount = 8;
69 
70   size_t count = 0;
71   PropertyName* protoName = runtimeFromMainThread()->commonNames->proto;
72 
73   for (ShapePropertyIter<NoGC> iter(shape()); !iter.done(); iter++) {
74     // __proto__ is always allowed.
75     if (iter->key().isAtom(protoName)) {
76       continue;
77     }
78 
79     MOZ_ASSERT(iter->isDataProperty());
80     MOZ_ASSERT(iter->writable());
81 
82     count++;
83     if (count > MaxCount) {
84       return;
85     }
86   }
87 }
88 #endif
89 
90 // static
createWithTemplateFromDifferentRealm(JSContext * cx,HandlePlainObject templateObject)91 PlainObject* PlainObject::createWithTemplateFromDifferentRealm(
92     JSContext* cx, HandlePlainObject templateObject) {
93   MOZ_ASSERT(cx->realm() != templateObject->realm(),
94              "Use createWithTemplate() for same-realm objects");
95 
96   // Currently only implemented for null-proto.
97   MOZ_ASSERT(templateObject->staticPrototype() == nullptr);
98 
99   // The object mustn't be in dictionary mode.
100   MOZ_ASSERT(!templateObject->shape()->isDictionary());
101 
102   TaggedProto proto = TaggedProto(nullptr);
103   Shape* templateShape = templateObject->shape();
104   Rooted<SharedPropMap*> map(cx, templateShape->propMap()->asShared());
105 
106   RootedShape shape(
107       cx, SharedShape::getInitialOrPropMapShape(
108               cx, &PlainObject::class_, cx->realm(), proto,
109               templateShape->numFixedSlots(), map,
110               templateShape->propMapLength(), templateShape->objectFlags()));
111   if (!shape) {
112     return nullptr;
113   }
114   return createWithShape(cx, shape);
115 }
116 
AddPlainObjectProperties(JSContext * cx,HandlePlainObject obj,IdValuePair * properties,size_t nproperties)117 static bool AddPlainObjectProperties(JSContext* cx, HandlePlainObject obj,
118                                      IdValuePair* properties,
119                                      size_t nproperties) {
120   RootedId propid(cx);
121   RootedValue value(cx);
122 
123   for (size_t i = 0; i < nproperties; i++) {
124     propid = properties[i].id;
125     value = properties[i].value;
126     if (!NativeDefineDataProperty(cx, obj, propid, value, JSPROP_ENUMERATE)) {
127       return false;
128     }
129   }
130 
131   return true;
132 }
133 
134 // static
createPlainObjectShapeWithDefaultProto(JSContext * cx,gc::AllocKind kind)135 Shape* GlobalObject::createPlainObjectShapeWithDefaultProto(
136     JSContext* cx, gc::AllocKind kind) {
137   PlainObjectSlotsKind slotsKind = PlainObjectSlotsKindFromAllocKind(kind);
138   HeapPtr<Shape*>& shapeRef =
139       cx->global()->data().plainObjectShapesWithDefaultProto[slotsKind];
140   MOZ_ASSERT(!shapeRef);
141 
142   JSObject* proto = GlobalObject::getOrCreatePrototype(cx, JSProto_Object);
143   if (!proto) {
144     return nullptr;
145   }
146 
147   Shape* shape = GetPlainObjectShapeWithProto(cx, proto, kind);
148   if (!shape) {
149     return nullptr;
150   }
151 
152   shapeRef.init(shape);
153   return shape;
154 }
155 
NewPlainObject(JSContext * cx,NewObjectKind newKind)156 PlainObject* js::NewPlainObject(JSContext* cx, NewObjectKind newKind) {
157   constexpr gc::AllocKind allocKind = gc::AllocKind::OBJECT0;
158   MOZ_ASSERT(gc::GetGCObjectKind(&PlainObject::class_) == allocKind);
159 
160   RootedShape shape(
161       cx, GlobalObject::getPlainObjectShapeWithDefaultProto(cx, allocKind));
162   if (!shape) {
163     return nullptr;
164   }
165 
166   return PlainObject::createWithShape(cx, shape, allocKind, newKind);
167 }
168 
NewPlainObjectWithAllocKind(JSContext * cx,gc::AllocKind allocKind,NewObjectKind newKind)169 PlainObject* js::NewPlainObjectWithAllocKind(JSContext* cx,
170                                              gc::AllocKind allocKind,
171                                              NewObjectKind newKind) {
172   RootedShape shape(
173       cx, GlobalObject::getPlainObjectShapeWithDefaultProto(cx, allocKind));
174   if (!shape) {
175     return nullptr;
176   }
177 
178   return PlainObject::createWithShape(cx, shape, allocKind, newKind);
179 }
180 
NewPlainObjectWithProto(JSContext * cx,HandleObject proto,NewObjectKind newKind)181 PlainObject* js::NewPlainObjectWithProto(JSContext* cx, HandleObject proto,
182                                          NewObjectKind newKind) {
183   // Use a faster path if |proto| is %Object.prototype% (the common case).
184   if (proto && proto == cx->global()->maybeGetPrototype(JSProto_Object)) {
185     return NewPlainObject(cx, newKind);
186   }
187 
188   constexpr gc::AllocKind allocKind = gc::AllocKind::OBJECT0;
189   MOZ_ASSERT(gc::GetGCObjectKind(&PlainObject::class_) == allocKind);
190 
191   RootedShape shape(cx, GetPlainObjectShapeWithProto(cx, proto, allocKind));
192   if (!shape) {
193     return nullptr;
194   }
195 
196   return PlainObject::createWithShape(cx, shape, allocKind, newKind);
197 }
198 
NewPlainObjectWithProtoAndAllocKind(JSContext * cx,HandleObject proto,gc::AllocKind allocKind,NewObjectKind newKind)199 PlainObject* js::NewPlainObjectWithProtoAndAllocKind(JSContext* cx,
200                                                      HandleObject proto,
201                                                      gc::AllocKind allocKind,
202                                                      NewObjectKind newKind) {
203   // Use a faster path if |proto| is %Object.prototype% (the common case).
204   if (proto && proto == cx->global()->maybeGetPrototype(JSProto_Object)) {
205     return NewPlainObjectWithAllocKind(cx, allocKind, newKind);
206   }
207 
208   RootedShape shape(cx, GetPlainObjectShapeWithProto(cx, proto, allocKind));
209   if (!shape) {
210     return nullptr;
211   }
212 
213   return PlainObject::createWithShape(cx, shape, allocKind, newKind);
214 }
215 
NewPlainObjectWithProperties(JSContext * cx,IdValuePair * properties,size_t nproperties,NewObjectKind newKind)216 PlainObject* js::NewPlainObjectWithProperties(JSContext* cx,
217                                               IdValuePair* properties,
218                                               size_t nproperties,
219                                               NewObjectKind newKind) {
220   gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties);
221   RootedPlainObject obj(cx,
222                         NewPlainObjectWithAllocKind(cx, allocKind, newKind));
223   if (!obj || !AddPlainObjectProperties(cx, obj, properties, nproperties)) {
224     return nullptr;
225   }
226   return obj;
227 }
228