PlainObject.cpp (10708B)
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/JSFunction-inl.h" 25 #include "vm/JSObject-inl.h" // js::NewObjectWithGroup, js::NewObjectGCKind 26 27 using namespace js; 28 29 using JS::Handle; 30 using JS::Rooted; 31 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); 39 } 40 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()); 45 46 Rooted<JSObject*> proto(cx); 47 if (!GetPrototypeFromConstructor(cx, newTarget, JSProto_Object, &proto)) { 48 return nullptr; 49 } 50 51 js::gc::AllocKind allocKind = NewObjectGCKind(); 52 if (!JSFunction::getAllocKindForThis(cx, callee, allocKind)) { 53 return nullptr; 54 } 55 56 SharedShape* res; 57 if (proto && proto != cx->global()->maybeGetPrototype(JSProto_Object)) { 58 res = GetPlainObjectShapeWithProto(cx, proto, allocKind); 59 } else { 60 res = GlobalObject::getPlainObjectShapeWithDefaultProto(cx, allocKind); 61 } 62 63 MOZ_ASSERT_IF(res, res->realm() == callee->realm()); 64 65 return res; 66 } 67 68 #ifdef DEBUG 69 void PlainObject::assertHasNoNonWritableOrAccessorPropExclProto() const { 70 // Check the most recent MaxCount properties to not slow down debug builds too 71 // much. 72 static constexpr size_t MaxCount = 8; 73 74 size_t count = 0; 75 PropertyName* protoName = runtimeFromMainThread()->commonNames->proto_; 76 77 for (ShapePropertyIter<NoGC> iter(shape()); !iter.done(); iter++) { 78 // __proto__ is always allowed. 79 if (iter->key().isAtom(protoName)) { 80 continue; 81 } 82 83 MOZ_ASSERT(iter->isDataProperty()); 84 MOZ_ASSERT(iter->writable()); 85 86 count++; 87 if (count > MaxCount) { 88 return; 89 } 90 } 91 } 92 #endif 93 94 // static 95 PlainObject* PlainObject::createWithTemplateFromDifferentRealm( 96 JSContext* cx, Handle<PlainObject*> templateObject) { 97 MOZ_ASSERT(cx->realm() != templateObject->realm(), 98 "Use createWithTemplate() for same-realm objects"); 99 100 // Currently only implemented for null-proto. 101 MOZ_ASSERT(templateObject->staticPrototype() == nullptr); 102 103 // The object mustn't be in dictionary mode. 104 MOZ_ASSERT(!templateObject->shape()->isDictionary()); 105 106 TaggedProto proto = TaggedProto(nullptr); 107 SharedShape* templateShape = templateObject->sharedShape(); 108 Rooted<SharedPropMap*> map(cx, templateShape->propMap()); 109 110 Rooted<SharedShape*> shape( 111 cx, SharedShape::getInitialOrPropMapShape( 112 cx, &PlainObject::class_, cx->realm(), proto, 113 templateShape->numFixedSlots(), map, 114 templateShape->propMapLength(), templateShape->objectFlags())); 115 if (!shape) { 116 return nullptr; 117 } 118 return createWithShape(cx, shape); 119 } 120 121 // static 122 SharedShape* GlobalObject::createPlainObjectShapeWithDefaultProto( 123 JSContext* cx, gc::AllocKind kind) { 124 PlainObjectSlotsKind slotsKind = PlainObjectSlotsKindFromAllocKind(kind); 125 GCPtr<SharedShape*>& shapeRef = 126 cx->global()->data().plainObjectShapesWithDefaultProto[slotsKind]; 127 MOZ_ASSERT(!shapeRef); 128 129 JSObject* proto = &cx->global()->getObjectPrototype(); 130 SharedShape* shape = GetPlainObjectShapeWithProto(cx, proto, kind); 131 if (!shape) { 132 return nullptr; 133 } 134 135 shapeRef.init(shape); 136 return shape; 137 } 138 139 PlainObject* js::NewPlainObject(JSContext* cx, NewObjectKind newKind) { 140 constexpr gc::AllocKind allocKind = gc::AllocKind::OBJECT0; 141 MOZ_ASSERT(gc::GetGCObjectKind(&PlainObject::class_) == allocKind); 142 143 Rooted<SharedShape*> shape( 144 cx, GlobalObject::getPlainObjectShapeWithDefaultProto(cx, allocKind)); 145 if (!shape) { 146 return nullptr; 147 } 148 149 return PlainObject::createWithShape(cx, shape, allocKind, newKind); 150 } 151 152 PlainObject* js::NewPlainObjectWithAllocKind(JSContext* cx, 153 gc::AllocKind allocKind, 154 NewObjectKind newKind) { 155 Rooted<SharedShape*> shape( 156 cx, GlobalObject::getPlainObjectShapeWithDefaultProto(cx, allocKind)); 157 if (!shape) { 158 return nullptr; 159 } 160 161 return PlainObject::createWithShape(cx, shape, allocKind, newKind); 162 } 163 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); 169 } 170 171 constexpr gc::AllocKind allocKind = gc::AllocKind::OBJECT0; 172 MOZ_ASSERT(gc::GetGCObjectKind(&PlainObject::class_) == allocKind); 173 174 Rooted<SharedShape*> shape( 175 cx, GetPlainObjectShapeWithProto(cx, proto, allocKind)); 176 if (!shape) { 177 return nullptr; 178 } 179 180 return PlainObject::createWithShape(cx, shape, allocKind, newKind); 181 } 182 183 PlainObject* js::NewPlainObjectWithProtoAndAllocKind(JSContext* cx, 184 HandleObject proto, 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); 190 } 191 192 Rooted<SharedShape*> shape( 193 cx, GetPlainObjectShapeWithProto(cx, proto, allocKind)); 194 if (!shape) { 195 return nullptr; 196 } 197 198 return PlainObject::createWithShape(cx, shape, allocKind, newKind); 199 } 200 201 void js::NewPlainObjectWithPropsCache::add(SharedShape* shape) { 202 MOZ_ASSERT(shape); 203 MOZ_ASSERT(shape->slotSpan() > 0); 204 for (size_t i = NumEntries - 1; i > 0; i--) { 205 entries_[i] = entries_[i - 1]; 206 } 207 entries_[0] = shape; 208 } 209 210 static bool ShapeMatches(Handle<IdValueVector> properties, SharedShape* shape) { 211 if (shape->slotSpan() != properties.length()) { 212 return false; 213 } 214 SharedShapePropertyIter<NoGC> iter(shape); 215 for (size_t i = properties.length(); i > 0; i--) { 216 MOZ_ASSERT(iter->isDataProperty()); 217 MOZ_ASSERT(iter->flags() == PropertyFlags::defaultDataPropFlags); 218 if (properties[i - 1].get().id != iter->key()) { 219 return false; 220 } 221 iter++; 222 } 223 MOZ_ASSERT(iter.done()); 224 return true; 225 } 226 227 SharedShape* js::NewPlainObjectWithPropsCache::lookup( 228 Handle<IdValueVector> properties) const { 229 for (size_t i = 0; i < NumEntries; i++) { 230 SharedShape* shape = entries_[i]; 231 if (shape && ShapeMatches(properties, shape)) { 232 return shape; 233 } 234 } 235 return nullptr; 236 } 237 238 enum class KeysKind { UniqueNames, Unknown }; 239 240 template <KeysKind Kind> 241 static PlainObject* NewPlainObjectWithProperties( 242 JSContext* cx, Handle<IdValueVector> properties, NewObjectKind newKind) { 243 auto& cache = cx->realm()->newPlainObjectWithPropsCache; 244 245 // If we recently created an object with these properties, we can use that 246 // Shape directly. 247 if (SharedShape* shape = cache.lookup(properties)) { 248 Rooted<SharedShape*> shapeRoot(cx, shape); 249 PlainObject* obj = PlainObject::createWithShape(cx, shapeRoot, newKind); 250 if (!obj) { 251 return nullptr; 252 } 253 MOZ_ASSERT(obj->slotSpan() == properties.length()); 254 for (size_t i = 0; i < properties.length(); i++) { 255 obj->initSlot(i, properties[i].get().value); 256 } 257 return obj; 258 } 259 260 gc::AllocKind allocKind = gc::GetGCObjectKind(properties.length()); 261 Rooted<PlainObject*> obj(cx, 262 NewPlainObjectWithAllocKind(cx, allocKind, newKind)); 263 if (!obj) { 264 return nullptr; 265 } 266 267 if (properties.empty()) { 268 return obj; 269 } 270 271 Rooted<PropertyKey> key(cx); 272 Rooted<Value> value(cx); 273 bool canCache = true; 274 275 for (const auto& prop : properties) { 276 key = prop.id; 277 value = prop.value; 278 279 // Integer keys may need to be stored in dense elements. This is uncommon so 280 // just fall back to NativeDefineDataProperty. 281 if constexpr (Kind == KeysKind::Unknown) { 282 if (MOZ_UNLIKELY(key.isInt())) { 283 canCache = false; 284 if (!NativeDefineDataProperty(cx, obj, key, value, JSPROP_ENUMERATE)) { 285 return nullptr; 286 } 287 continue; 288 } 289 } 290 291 MOZ_ASSERT(key.isAtom() || key.isSymbol()); 292 293 // Check for duplicate keys. In this case we must overwrite the earlier 294 // property value. 295 if constexpr (Kind == KeysKind::UniqueNames) { 296 MOZ_ASSERT(!obj->containsPure(key)); 297 } else { 298 mozilla::Maybe<PropertyInfo> prop = obj->lookup(cx, key); 299 if (MOZ_UNLIKELY(prop)) { 300 canCache = false; 301 MOZ_ASSERT(prop->isDataProperty()); 302 obj->setSlot(prop->slot(), value); 303 continue; 304 } 305 } 306 307 if (!AddDataPropertyToPlainObject(cx, obj, key, value)) { 308 return nullptr; 309 } 310 } 311 312 if (canCache && !obj->inDictionaryMode()) { 313 MOZ_ASSERT(obj->getDenseInitializedLength() == 0); 314 MOZ_ASSERT(obj->slotSpan() == properties.length()); 315 cache.add(obj->sharedShape()); 316 } 317 318 return obj; 319 } 320 321 PlainObject* js::NewPlainObjectWithUniqueNames(JSContext* cx, 322 Handle<IdValueVector> properties, 323 NewObjectKind newKind) { 324 return NewPlainObjectWithProperties<KeysKind::UniqueNames>(cx, properties, 325 newKind); 326 } 327 328 PlainObject* js::NewPlainObjectWithMaybeDuplicateKeys( 329 JSContext* cx, Handle<IdValueVector> properties, NewObjectKind newKind) { 330 return NewPlainObjectWithProperties<KeysKind::Unknown>(cx, properties, 331 newKind); 332 }