1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
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 #include "builtin/MapObject.h"
8
9 #include "jscntxt.h"
10 #include "jsiter.h"
11 #include "jsobj.h"
12
13 #include "ds/OrderedHashTable.h"
14 #include "gc/Marking.h"
15 #include "js/Utility.h"
16 #include "vm/GlobalObject.h"
17 #include "vm/Interpreter.h"
18 #include "vm/SelfHosting.h"
19 #include "vm/Symbol.h"
20
21 #include "jsobjinlines.h"
22
23 #include "vm/Interpreter-inl.h"
24 #include "vm/NativeObject-inl.h"
25
26 using namespace js;
27
28 using mozilla::ArrayLength;
29 using mozilla::IsNaN;
30 using mozilla::NumberEqualsInt32;
31
32 using JS::DoubleNaNValue;
33 using JS::ForOfIterator;
34
35
36 /*** HashableValue *******************************************************************************/
37
38 bool
setValue(JSContext * cx,HandleValue v)39 HashableValue::setValue(JSContext* cx, HandleValue v)
40 {
41 if (v.isString()) {
42 // Atomize so that hash() and operator==() are fast and infallible.
43 JSString* str = AtomizeString(cx, v.toString(), DoNotPinAtom);
44 if (!str)
45 return false;
46 value = StringValue(str);
47 } else if (v.isDouble()) {
48 double d = v.toDouble();
49 int32_t i;
50 if (NumberEqualsInt32(d, &i)) {
51 // Normalize int32_t-valued doubles to int32_t for faster hashing and testing.
52 value = Int32Value(i);
53 } else if (IsNaN(d)) {
54 // NaNs with different bits must hash and test identically.
55 value = DoubleNaNValue();
56 } else {
57 value = v;
58 }
59 } else {
60 value = v;
61 }
62
63 MOZ_ASSERT(value.isUndefined() || value.isNull() || value.isBoolean() || value.isNumber() ||
64 value.isString() || value.isSymbol() || value.isObject());
65 return true;
66 }
67
68 static HashNumber
HashValue(const Value & v,const mozilla::HashCodeScrambler & hcs)69 HashValue(const Value& v, const mozilla::HashCodeScrambler& hcs)
70 {
71 // HashableValue::setValue normalizes values so that the SameValue relation
72 // on HashableValues is the same as the == relationship on
73 // value.asRawBits(). So why not just return that? Security.
74 //
75 // To avoid revealing GC of atoms, string-based hash codes are computed
76 // from the string contents rather than any pointer; to avoid revealing
77 // addresses, pointer-based hash codes are computed using the
78 // HashCodeScrambler.
79
80 if (v.isString())
81 return v.toString()->asAtom().hash();
82 if (v.isSymbol())
83 return v.toSymbol()->hash();
84 if (v.isObject())
85 return hcs.scramble(v.asRawBits());
86
87 MOZ_ASSERT(v.isNull() || !v.isGCThing(), "do not reveal pointers via hash codes");
88 return v.asRawBits();
89 }
90
91 HashNumber
hash(const mozilla::HashCodeScrambler & hcs) const92 HashableValue::hash(const mozilla::HashCodeScrambler& hcs) const
93 {
94 return HashValue(value, hcs);
95 }
96
97 bool
operator ==(const HashableValue & other) const98 HashableValue::operator==(const HashableValue& other) const
99 {
100 // Two HashableValues are equal if they have equal bits.
101 bool b = (value.asRawBits() == other.value.asRawBits());
102
103 #ifdef DEBUG
104 bool same;
105 JS::RootingContext* rcx = GetJSContextFromMainThread();
106 RootedValue valueRoot(rcx, value);
107 RootedValue otherRoot(rcx, other.value);
108 MOZ_ASSERT(SameValue(nullptr, valueRoot, otherRoot, &same));
109 MOZ_ASSERT(same == b);
110 #endif
111 return b;
112 }
113
114 HashableValue
mark(JSTracer * trc) const115 HashableValue::mark(JSTracer* trc) const
116 {
117 HashableValue hv(*this);
118 TraceEdge(trc, &hv.value, "key");
119 return hv;
120 }
121
122
123 /*** MapIterator *********************************************************************************/
124
125 namespace {
126
127 } /* anonymous namespace */
128
129 static const ClassOps MapIteratorObjectClassOps = {
130 nullptr, /* addProperty */
131 nullptr, /* delProperty */
132 nullptr, /* getProperty */
133 nullptr, /* setProperty */
134 nullptr, /* enumerate */
135 nullptr, /* resolve */
136 nullptr, /* mayResolve */
137 MapIteratorObject::finalize
138 };
139
140 const Class MapIteratorObject::class_ = {
141 "Map Iterator",
142 JSCLASS_HAS_RESERVED_SLOTS(MapIteratorObject::SlotCount) |
143 JSCLASS_FOREGROUND_FINALIZE,
144 &MapIteratorObjectClassOps
145 };
146
147 const JSFunctionSpec MapIteratorObject::methods[] = {
148 JS_SELF_HOSTED_FN("next", "MapIteratorNext", 0, 0),
149 JS_FS_END
150 };
151
152 static inline ValueMap::Range*
MapIteratorObjectRange(NativeObject * obj)153 MapIteratorObjectRange(NativeObject* obj)
154 {
155 MOZ_ASSERT(obj->is<MapIteratorObject>());
156 return static_cast<ValueMap::Range*>(obj->getSlot(MapIteratorObject::RangeSlot).toPrivate());
157 }
158
159 inline MapObject::IteratorKind
kind() const160 MapIteratorObject::kind() const
161 {
162 int32_t i = getSlot(KindSlot).toInt32();
163 MOZ_ASSERT(i == MapObject::Keys || i == MapObject::Values || i == MapObject::Entries);
164 return MapObject::IteratorKind(i);
165 }
166
167 bool
initMapIteratorProto(JSContext * cx,Handle<GlobalObject * > global)168 GlobalObject::initMapIteratorProto(JSContext* cx, Handle<GlobalObject*> global)
169 {
170 Rooted<JSObject*> base(cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
171 if (!base)
172 return false;
173 RootedPlainObject proto(cx, NewObjectWithGivenProto<PlainObject>(cx, base));
174 if (!proto)
175 return false;
176 if (!JS_DefineFunctions(cx, proto, MapIteratorObject::methods) ||
177 !DefineToStringTag(cx, proto, cx->names().MapIterator))
178 {
179 return false;
180 }
181 global->setReservedSlot(MAP_ITERATOR_PROTO, ObjectValue(*proto));
182 return true;
183 }
184
185 MapIteratorObject*
create(JSContext * cx,HandleObject mapobj,ValueMap * data,MapObject::IteratorKind kind)186 MapIteratorObject::create(JSContext* cx, HandleObject mapobj, ValueMap* data,
187 MapObject::IteratorKind kind)
188 {
189 Rooted<GlobalObject*> global(cx, &mapobj->global());
190 Rooted<JSObject*> proto(cx, GlobalObject::getOrCreateMapIteratorPrototype(cx, global));
191 if (!proto)
192 return nullptr;
193
194 ValueMap::Range* range = cx->new_<ValueMap::Range>(data->all());
195 if (!range)
196 return nullptr;
197
198 MapIteratorObject* iterobj = NewObjectWithGivenProto<MapIteratorObject>(cx, proto);
199 if (!iterobj) {
200 js_delete(range);
201 return nullptr;
202 }
203 iterobj->setSlot(TargetSlot, ObjectValue(*mapobj));
204 iterobj->setSlot(RangeSlot, PrivateValue(range));
205 iterobj->setSlot(KindSlot, Int32Value(int32_t(kind)));
206 return iterobj;
207 }
208
209 void
finalize(FreeOp * fop,JSObject * obj)210 MapIteratorObject::finalize(FreeOp* fop, JSObject* obj)
211 {
212 MOZ_ASSERT(fop->onMainThread());
213 fop->delete_(MapIteratorObjectRange(static_cast<NativeObject*>(obj)));
214 }
215
216 bool
next(Handle<MapIteratorObject * > mapIterator,HandleArrayObject resultPairObj,JSContext * cx)217 MapIteratorObject::next(Handle<MapIteratorObject*> mapIterator, HandleArrayObject resultPairObj,
218 JSContext* cx)
219 {
220 // Check invariants for inlined _GetNextMapEntryForIterator.
221
222 // The array should be tenured, so that post-barrier can be done simply.
223 MOZ_ASSERT(resultPairObj->isTenured());
224
225 // The array elements should be fixed.
226 MOZ_ASSERT(resultPairObj->hasFixedElements());
227 MOZ_ASSERT(resultPairObj->getDenseInitializedLength() == 2);
228 MOZ_ASSERT(resultPairObj->getDenseCapacity() >= 2);
229
230 ValueMap::Range* range = MapIteratorObjectRange(mapIterator);
231 if (!range || range->empty()) {
232 js_delete(range);
233 mapIterator->setReservedSlot(RangeSlot, PrivateValue(nullptr));
234 return true;
235 }
236 switch (mapIterator->kind()) {
237 case MapObject::Keys:
238 resultPairObj->setDenseElementWithType(cx, 0, range->front().key.get());
239 break;
240
241 case MapObject::Values:
242 resultPairObj->setDenseElementWithType(cx, 1, range->front().value);
243 break;
244
245 case MapObject::Entries: {
246 resultPairObj->setDenseElementWithType(cx, 0, range->front().key.get());
247 resultPairObj->setDenseElementWithType(cx, 1, range->front().value);
248 break;
249 }
250 }
251 range->popFront();
252 return false;
253 }
254
255 /* static */ JSObject*
createResultPair(JSContext * cx)256 MapIteratorObject::createResultPair(JSContext* cx)
257 {
258 RootedArrayObject resultPairObj(cx, NewDenseFullyAllocatedArray(cx, 2, nullptr, TenuredObject));
259 if (!resultPairObj)
260 return nullptr;
261
262 Rooted<TaggedProto> proto(cx, resultPairObj->taggedProto());
263 ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, resultPairObj->getClass(), proto);
264 if (!group)
265 return nullptr;
266 resultPairObj->setGroup(group);
267
268 resultPairObj->setDenseInitializedLength(2);
269 resultPairObj->initDenseElement(0, NullValue());
270 resultPairObj->initDenseElement(1, NullValue());
271
272 // See comments in MapIteratorObject::next.
273 AddTypePropertyId(cx, resultPairObj, JSID_VOID, TypeSet::UnknownType());
274
275 return resultPairObj;
276 }
277
278
279 /*** Map *****************************************************************************************/
280
281 const ClassOps MapObject::classOps_ = {
282 nullptr, // addProperty
283 nullptr, // delProperty
284 nullptr, // getProperty
285 nullptr, // setProperty
286 nullptr, // enumerate
287 nullptr, // resolve
288 nullptr, // mayResolve
289 finalize,
290 nullptr, // call
291 nullptr, // hasInstance
292 nullptr, // construct
293 mark
294 };
295
296 const Class MapObject::class_ = {
297 "Map",
298 JSCLASS_HAS_PRIVATE |
299 JSCLASS_HAS_RESERVED_SLOTS(MapObject::SlotCount) |
300 JSCLASS_HAS_CACHED_PROTO(JSProto_Map) |
301 JSCLASS_FOREGROUND_FINALIZE,
302 &MapObject::classOps_
303 };
304
305 const JSPropertySpec MapObject::properties[] = {
306 JS_PSG("size", size, 0),
307 JS_PS_END
308 };
309
310 const JSFunctionSpec MapObject::methods[] = {
311 JS_FN("get", get, 1, 0),
312 JS_FN("has", has, 1, 0),
313 JS_FN("set", set, 2, 0),
314 JS_FN("delete", delete_, 1, 0),
315 JS_FN("keys", keys, 0, 0),
316 JS_FN("values", values, 0, 0),
317 JS_FN("clear", clear, 0, 0),
318 JS_SELF_HOSTED_FN("forEach", "MapForEach", 2, 0),
319 JS_FS_END
320 };
321
322 const JSPropertySpec MapObject::staticProperties[] = {
323 JS_SELF_HOSTED_SYM_GET(species, "MapSpecies", 0),
324 JS_PS_END
325 };
326
327 static JSObject*
InitClass(JSContext * cx,Handle<GlobalObject * > global,const Class * clasp,JSProtoKey key,Native construct,const JSPropertySpec * properties,const JSFunctionSpec * methods,const JSPropertySpec * staticProperties)328 InitClass(JSContext* cx, Handle<GlobalObject*> global, const Class* clasp, JSProtoKey key, Native construct,
329 const JSPropertySpec* properties, const JSFunctionSpec* methods,
330 const JSPropertySpec* staticProperties)
331 {
332 RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
333 if (!proto)
334 return nullptr;
335
336 Rooted<JSFunction*> ctor(cx, global->createConstructor(cx, construct, ClassName(key, cx), 0));
337 if (!ctor ||
338 !JS_DefineProperties(cx, ctor, staticProperties) ||
339 !LinkConstructorAndPrototype(cx, ctor, proto) ||
340 !DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
341 !GlobalObject::initBuiltinConstructor(cx, global, key, ctor, proto))
342 {
343 return nullptr;
344 }
345 return proto;
346 }
347
348 JSObject*
initClass(JSContext * cx,JSObject * obj)349 MapObject::initClass(JSContext* cx, JSObject* obj)
350 {
351 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
352 RootedObject proto(cx,
353 InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods,
354 staticProperties));
355 if (proto) {
356 // Define the "entries" method.
357 JSFunction* fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0);
358 if (!fun)
359 return nullptr;
360
361 // Define its alias.
362 RootedValue funval(cx, ObjectValue(*fun));
363 RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator));
364 if (!JS_DefinePropertyById(cx, proto, iteratorId, funval, 0))
365 return nullptr;
366
367 // Define Map.prototype[@@toStringTag].
368 if (!DefineToStringTag(cx, proto, cx->names().Map))
369 return nullptr;
370 }
371 return proto;
372 }
373
374 template <class Range>
375 static void
MarkKey(Range & r,const HashableValue & key,JSTracer * trc)376 MarkKey(Range& r, const HashableValue& key, JSTracer* trc)
377 {
378 HashableValue newKey = key.mark(trc);
379
380 if (newKey.get() != key.get()) {
381 // The hash function only uses the bits of the Value, so it is safe to
382 // rekey even when the object or string has been modified by the GC.
383 r.rekeyFront(newKey);
384 }
385 }
386
387 void
mark(JSTracer * trc,JSObject * obj)388 MapObject::mark(JSTracer* trc, JSObject* obj)
389 {
390 if (ValueMap* map = obj->as<MapObject>().getData()) {
391 for (ValueMap::Range r = map->all(); !r.empty(); r.popFront()) {
392 MarkKey(r, r.front().key, trc);
393 TraceEdge(trc, &r.front().value, "value");
394 }
395 }
396 }
397
398 struct js::UnbarrieredHashPolicy {
399 typedef Value Lookup;
hashjs::UnbarrieredHashPolicy400 static HashNumber hash(const Lookup& v, const mozilla::HashCodeScrambler& hcs) {
401 return HashValue(v, hcs);
402 }
matchjs::UnbarrieredHashPolicy403 static bool match(const Value& k, const Lookup& l) { return k == l; }
isEmptyjs::UnbarrieredHashPolicy404 static bool isEmpty(const Value& v) { return v.isMagic(JS_HASH_KEY_EMPTY); }
makeEmptyjs::UnbarrieredHashPolicy405 static void makeEmpty(Value* vp) { vp->setMagic(JS_HASH_KEY_EMPTY); }
406 };
407
408 using NurseryKeysVector = Vector<JSObject*, 0, SystemAllocPolicy>;
409
410 template <typename TableObject>
411 static NurseryKeysVector*
GetNurseryKeys(TableObject * t)412 GetNurseryKeys(TableObject* t)
413 {
414 Value value = t->getReservedSlot(TableObject::NurseryKeysSlot);
415 return reinterpret_cast<NurseryKeysVector*>(value.toPrivate());
416 }
417
418 template <typename TableObject>
419 static NurseryKeysVector*
AllocNurseryKeys(TableObject * t)420 AllocNurseryKeys(TableObject* t)
421 {
422 MOZ_ASSERT(!GetNurseryKeys(t));
423 auto keys = js_new<NurseryKeysVector>();
424 if (!keys)
425 return nullptr;
426
427 t->setReservedSlot(TableObject::NurseryKeysSlot, PrivateValue(keys));
428 return keys;
429 }
430
431 template <typename TableObject>
432 static void
DeleteNurseryKeys(TableObject * t)433 DeleteNurseryKeys(TableObject* t)
434 {
435 auto keys = GetNurseryKeys(t);
436 MOZ_ASSERT(keys);
437 js_delete(keys);
438 t->setReservedSlot(TableObject::NurseryKeysSlot, PrivateValue(nullptr));
439 }
440
441 // A generic store buffer entry that traces all nursery keys for an ordered hash
442 // map or set.
443 template <typename ObjectT>
444 class js::OrderedHashTableRef : public gc::BufferableRef
445 {
446 ObjectT* object;
447
448 public:
OrderedHashTableRef(ObjectT * obj)449 explicit OrderedHashTableRef(ObjectT* obj) : object(obj) {}
450
trace(JSTracer * trc)451 void trace(JSTracer* trc) override {
452 auto realTable = object->getData();
453 auto unbarrieredTable = reinterpret_cast<typename ObjectT::UnbarrieredTable*>(realTable);
454 NurseryKeysVector* keys = GetNurseryKeys(object);
455 MOZ_ASSERT(keys);
456 for (JSObject* obj : *keys) {
457 MOZ_ASSERT(obj);
458 Value key = ObjectValue(*obj);
459 Value prior = key;
460 MOZ_ASSERT(unbarrieredTable->hash(key) ==
461 realTable->hash(*reinterpret_cast<HashableValue*>(&key)));
462 TraceManuallyBarrieredEdge(trc, &key, "ordered hash table key");
463 unbarrieredTable->rekeyOneEntry(prior, key);
464 }
465 DeleteNurseryKeys(object);
466 }
467 };
468
469 template <typename ObjectT>
470 inline static MOZ_MUST_USE bool
WriteBarrierPostImpl(JSRuntime * rt,ObjectT * obj,const Value & keyValue)471 WriteBarrierPostImpl(JSRuntime* rt, ObjectT* obj, const Value& keyValue)
472 {
473 if (MOZ_LIKELY(!keyValue.isObject()))
474 return true;
475
476 JSObject* key = &keyValue.toObject();
477 if (!IsInsideNursery(key))
478 return true;
479
480 NurseryKeysVector* keys = GetNurseryKeys(obj);
481 if (!keys) {
482 keys = AllocNurseryKeys(obj);
483 if (!keys)
484 return false;
485
486 rt->gc.storeBuffer.putGeneric(OrderedHashTableRef<ObjectT>(obj));
487 }
488
489 if (!keys->append(key))
490 return false;
491
492 return true;
493 }
494
495 inline static MOZ_MUST_USE bool
WriteBarrierPost(JSRuntime * rt,MapObject * map,const Value & key)496 WriteBarrierPost(JSRuntime* rt, MapObject* map, const Value& key)
497 {
498 return WriteBarrierPostImpl(rt, map, key);
499 }
500
501 inline static MOZ_MUST_USE bool
WriteBarrierPost(JSRuntime * rt,SetObject * set,const Value & key)502 WriteBarrierPost(JSRuntime* rt, SetObject* set, const Value& key)
503 {
504 return WriteBarrierPostImpl(rt, set, key);
505 }
506
507 bool
getKeysAndValuesInterleaved(JSContext * cx,HandleObject obj,JS::MutableHandle<GCVector<JS::Value>> entries)508 MapObject::getKeysAndValuesInterleaved(JSContext* cx, HandleObject obj,
509 JS::MutableHandle<GCVector<JS::Value>> entries)
510 {
511 ValueMap* map = obj->as<MapObject>().getData();
512 if (!map)
513 return false;
514
515 for (ValueMap::Range r = map->all(); !r.empty(); r.popFront()) {
516 if (!entries.append(r.front().key.get()) ||
517 !entries.append(r.front().value))
518 {
519 return false;
520 }
521 }
522
523 return true;
524 }
525
526 bool
set(JSContext * cx,HandleObject obj,HandleValue k,HandleValue v)527 MapObject::set(JSContext* cx, HandleObject obj, HandleValue k, HandleValue v)
528 {
529 ValueMap* map = obj->as<MapObject>().getData();
530 if (!map)
531 return false;
532
533 Rooted<HashableValue> key(cx);
534 if (!key.setValue(cx, k))
535 return false;
536
537 HeapPtr<Value> rval(v);
538 if (!WriteBarrierPost(cx->runtime(), &obj->as<MapObject>(), key.value()) ||
539 !map->put(key, rval))
540 {
541 ReportOutOfMemory(cx);
542 return false;
543 }
544
545 return true;
546 }
547
548 MapObject*
create(JSContext * cx,HandleObject proto)549 MapObject::create(JSContext* cx, HandleObject proto /* = nullptr */)
550 {
551 auto map = cx->make_unique<ValueMap>(cx->runtime(),
552 cx->compartment()->randomHashCodeScrambler());
553 if (!map || !map->init()) {
554 ReportOutOfMemory(cx);
555 return nullptr;
556 }
557
558 MapObject* mapObj = NewObjectWithClassProto<MapObject>(cx, proto);
559 if (!mapObj)
560 return nullptr;
561
562 mapObj->setPrivate(map.release());
563 mapObj->setReservedSlot(NurseryKeysSlot, PrivateValue(nullptr));
564 return mapObj;
565 }
566
567 void
finalize(FreeOp * fop,JSObject * obj)568 MapObject::finalize(FreeOp* fop, JSObject* obj)
569 {
570 MOZ_ASSERT(fop->onMainThread());
571 if (ValueMap* map = obj->as<MapObject>().getData())
572 fop->delete_(map);
573 }
574
575 bool
construct(JSContext * cx,unsigned argc,Value * vp)576 MapObject::construct(JSContext* cx, unsigned argc, Value* vp)
577 {
578 CallArgs args = CallArgsFromVp(argc, vp);
579
580 if (!ThrowIfNotConstructing(cx, args, "Map"))
581 return false;
582
583 RootedObject proto(cx);
584 RootedObject newTarget(cx, &args.newTarget().toObject());
585 if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
586 return false;
587
588 Rooted<MapObject*> obj(cx, MapObject::create(cx, proto));
589 if (!obj)
590 return false;
591
592 if (!args.get(0).isNullOrUndefined()) {
593 FixedInvokeArgs<1> args2(cx);
594 args2[0].set(args[0]);
595
596 RootedValue thisv(cx, ObjectValue(*obj));
597 if (!CallSelfHostedFunction(cx, cx->names().MapConstructorInit, thisv, args2, args2.rval()))
598 return false;
599 }
600
601 args.rval().setObject(*obj);
602 return true;
603 }
604
605 bool
is(HandleValue v)606 MapObject::is(HandleValue v)
607 {
608 return v.isObject() && v.toObject().hasClass(&class_) && v.toObject().as<MapObject>().getPrivate();
609 }
610
611 bool
is(HandleObject o)612 MapObject::is(HandleObject o)
613 {
614 return o->hasClass(&class_) && o->as<MapObject>().getPrivate();
615 }
616
617 #define ARG0_KEY(cx, args, key) \
618 Rooted<HashableValue> key(cx); \
619 if (args.length() > 0 && !key.setValue(cx, args[0])) \
620 return false
621
622 ValueMap&
extract(HandleObject o)623 MapObject::extract(HandleObject o)
624 {
625 MOZ_ASSERT(o->hasClass(&MapObject::class_));
626 return *o->as<MapObject>().getData();
627 }
628
629 ValueMap&
extract(const CallArgs & args)630 MapObject::extract(const CallArgs& args)
631 {
632 MOZ_ASSERT(args.thisv().isObject());
633 MOZ_ASSERT(args.thisv().toObject().hasClass(&MapObject::class_));
634 return *args.thisv().toObject().as<MapObject>().getData();
635 }
636
637 uint32_t
size(JSContext * cx,HandleObject obj)638 MapObject::size(JSContext* cx, HandleObject obj)
639 {
640 ValueMap& map = extract(obj);
641 static_assert(sizeof(map.count()) <= sizeof(uint32_t),
642 "map count must be precisely representable as a JS number");
643 return map.count();
644 }
645
646 bool
size_impl(JSContext * cx,const CallArgs & args)647 MapObject::size_impl(JSContext* cx, const CallArgs& args)
648 {
649 RootedObject obj(cx, &args.thisv().toObject());
650 args.rval().setNumber(size(cx, obj));
651 return true;
652 }
653
654 bool
size(JSContext * cx,unsigned argc,Value * vp)655 MapObject::size(JSContext* cx, unsigned argc, Value* vp)
656 {
657 CallArgs args = CallArgsFromVp(argc, vp);
658 return CallNonGenericMethod<MapObject::is, MapObject::size_impl>(cx, args);
659 }
660
661 bool
get(JSContext * cx,HandleObject obj,HandleValue key,MutableHandleValue rval)662 MapObject::get(JSContext* cx, HandleObject obj,
663 HandleValue key, MutableHandleValue rval)
664 {
665 ValueMap& map = extract(obj);
666 Rooted<HashableValue> k(cx);
667
668 if (!k.setValue(cx, key))
669 return false;
670
671 if (ValueMap::Entry* p = map.get(k))
672 rval.set(p->value);
673 else
674 rval.setUndefined();
675
676 return true;
677 }
678
679 bool
get_impl(JSContext * cx,const CallArgs & args)680 MapObject::get_impl(JSContext* cx, const CallArgs& args)
681 {
682 RootedObject obj(cx, &args.thisv().toObject());
683 return get(cx, obj, args.get(0), args.rval());
684 }
685
686 bool
get(JSContext * cx,unsigned argc,Value * vp)687 MapObject::get(JSContext* cx, unsigned argc, Value* vp)
688 {
689 CallArgs args = CallArgsFromVp(argc, vp);
690 return CallNonGenericMethod<MapObject::is, MapObject::get_impl>(cx, args);
691 }
692
693 bool
has(JSContext * cx,HandleObject obj,HandleValue key,bool * rval)694 MapObject::has(JSContext* cx, HandleObject obj, HandleValue key, bool* rval)
695 {
696 ValueMap& map = extract(obj);
697 Rooted<HashableValue> k(cx);
698
699 if (!k.setValue(cx, key))
700 return false;
701
702 *rval = map.has(k);
703 return true;
704 }
705
706 bool
has_impl(JSContext * cx,const CallArgs & args)707 MapObject::has_impl(JSContext* cx, const CallArgs& args)
708 {
709 bool found;
710 RootedObject obj(cx, &args.thisv().toObject());
711 if (has(cx, obj, args.get(0), &found)) {
712 args.rval().setBoolean(found);
713 return true;
714 }
715 return false;
716 }
717
718 bool
has(JSContext * cx,unsigned argc,Value * vp)719 MapObject::has(JSContext* cx, unsigned argc, Value* vp)
720 {
721 CallArgs args = CallArgsFromVp(argc, vp);
722 return CallNonGenericMethod<MapObject::is, MapObject::has_impl>(cx, args);
723 }
724
725 bool
set_impl(JSContext * cx,const CallArgs & args)726 MapObject::set_impl(JSContext* cx, const CallArgs& args)
727 {
728 MOZ_ASSERT(MapObject::is(args.thisv()));
729
730 ValueMap& map = extract(args);
731 ARG0_KEY(cx, args, key);
732 HeapPtr<Value> rval(args.get(1));
733 if (!WriteBarrierPost(cx->runtime(), &args.thisv().toObject().as<MapObject>(), key.value()) ||
734 !map.put(key, rval))
735 {
736 ReportOutOfMemory(cx);
737 return false;
738 }
739
740 args.rval().set(args.thisv());
741 return true;
742 }
743
744 bool
set(JSContext * cx,unsigned argc,Value * vp)745 MapObject::set(JSContext* cx, unsigned argc, Value* vp)
746 {
747 CallArgs args = CallArgsFromVp(argc, vp);
748 return CallNonGenericMethod<MapObject::is, MapObject::set_impl>(cx, args);
749 }
750
751 bool
delete_(JSContext * cx,HandleObject obj,HandleValue key,bool * rval)752 MapObject::delete_(JSContext *cx, HandleObject obj, HandleValue key, bool *rval)
753 {
754 ValueMap &map = extract(obj);
755 Rooted<HashableValue> k(cx);
756
757 if (!k.setValue(cx, key))
758 return false;
759
760 if (!map.remove(k, rval)) {
761 ReportOutOfMemory(cx);
762 return false;
763 }
764 return true;
765 }
766
767 bool
delete_impl(JSContext * cx,const CallArgs & args)768 MapObject::delete_impl(JSContext *cx, const CallArgs& args)
769 {
770 // MapObject::mark does not mark deleted entries. Incremental GC therefore
771 // requires that no HeapPtr<Value> objects pointing to heap values be left
772 // alive in the ValueMap.
773 //
774 // OrderedHashMap::remove() doesn't destroy the removed entry. It merely
775 // calls OrderedHashMap::MapOps::makeEmpty. But that is sufficient, because
776 // makeEmpty clears the value by doing e->value = Value(), and in the case
777 // of a ValueMap, Value() means HeapPtr<Value>(), which is the same as
778 // HeapPtr<Value>(UndefinedValue()).
779 MOZ_ASSERT(MapObject::is(args.thisv()));
780
781 ValueMap& map = extract(args);
782 ARG0_KEY(cx, args, key);
783 bool found;
784 if (!map.remove(key, &found)) {
785 ReportOutOfMemory(cx);
786 return false;
787 }
788 args.rval().setBoolean(found);
789 return true;
790 }
791
792 bool
delete_(JSContext * cx,unsigned argc,Value * vp)793 MapObject::delete_(JSContext* cx, unsigned argc, Value* vp)
794 {
795 CallArgs args = CallArgsFromVp(argc, vp);
796 return CallNonGenericMethod<MapObject::is, MapObject::delete_impl>(cx, args);
797 }
798
799 bool
iterator(JSContext * cx,IteratorKind kind,HandleObject obj,MutableHandleValue iter)800 MapObject::iterator(JSContext* cx, IteratorKind kind,
801 HandleObject obj, MutableHandleValue iter)
802 {
803 ValueMap& map = extract(obj);
804 Rooted<JSObject*> iterobj(cx, MapIteratorObject::create(cx, obj, &map, kind));
805 return iterobj && (iter.setObject(*iterobj), true);
806 }
807
808 bool
iterator_impl(JSContext * cx,const CallArgs & args,IteratorKind kind)809 MapObject::iterator_impl(JSContext* cx, const CallArgs& args, IteratorKind kind)
810 {
811 RootedObject obj(cx, &args.thisv().toObject());
812 return iterator(cx, kind, obj, args.rval());
813 }
814
815 bool
keys_impl(JSContext * cx,const CallArgs & args)816 MapObject::keys_impl(JSContext* cx, const CallArgs& args)
817 {
818 return iterator_impl(cx, args, Keys);
819 }
820
821 bool
keys(JSContext * cx,unsigned argc,Value * vp)822 MapObject::keys(JSContext* cx, unsigned argc, Value* vp)
823 {
824 CallArgs args = CallArgsFromVp(argc, vp);
825 return CallNonGenericMethod(cx, is, keys_impl, args);
826 }
827
828 bool
values_impl(JSContext * cx,const CallArgs & args)829 MapObject::values_impl(JSContext* cx, const CallArgs& args)
830 {
831 return iterator_impl(cx, args, Values);
832 }
833
834 bool
values(JSContext * cx,unsigned argc,Value * vp)835 MapObject::values(JSContext* cx, unsigned argc, Value* vp)
836 {
837 CallArgs args = CallArgsFromVp(argc, vp);
838 return CallNonGenericMethod(cx, is, values_impl, args);
839 }
840
841 bool
entries_impl(JSContext * cx,const CallArgs & args)842 MapObject::entries_impl(JSContext* cx, const CallArgs& args)
843 {
844 return iterator_impl(cx, args, Entries);
845 }
846
847 bool
entries(JSContext * cx,unsigned argc,Value * vp)848 MapObject::entries(JSContext* cx, unsigned argc, Value* vp)
849 {
850 CallArgs args = CallArgsFromVp(argc, vp);
851 return CallNonGenericMethod(cx, is, entries_impl, args);
852 }
853
854 bool
clear_impl(JSContext * cx,const CallArgs & args)855 MapObject::clear_impl(JSContext* cx, const CallArgs& args)
856 {
857 RootedObject obj(cx, &args.thisv().toObject());
858 args.rval().setUndefined();
859 return clear(cx, obj);
860 }
861
862 bool
clear(JSContext * cx,unsigned argc,Value * vp)863 MapObject::clear(JSContext* cx, unsigned argc, Value* vp)
864 {
865 CallArgs args = CallArgsFromVp(argc, vp);
866 return CallNonGenericMethod(cx, is, clear_impl, args);
867 }
868
869 bool
clear(JSContext * cx,HandleObject obj)870 MapObject::clear(JSContext* cx, HandleObject obj)
871 {
872 ValueMap& map = extract(obj);
873 if (!map.clear()) {
874 ReportOutOfMemory(cx);
875 return false;
876 }
877 return true;
878 }
879
880 JSObject*
InitMapClass(JSContext * cx,HandleObject obj)881 js::InitMapClass(JSContext* cx, HandleObject obj)
882 {
883 return MapObject::initClass(cx, obj);
884 }
885
886
887 /*** SetIterator *********************************************************************************/
888
889 static const ClassOps SetIteratorObjectClassOps = {
890 nullptr, /* addProperty */
891 nullptr, /* delProperty */
892 nullptr, /* getProperty */
893 nullptr, /* setProperty */
894 nullptr, /* enumerate */
895 nullptr, /* resolve */
896 nullptr, /* mayResolve */
897 SetIteratorObject::finalize
898 };
899
900 const Class SetIteratorObject::class_ = {
901 "Set Iterator",
902 JSCLASS_HAS_RESERVED_SLOTS(SetIteratorObject::SlotCount) |
903 JSCLASS_FOREGROUND_FINALIZE,
904 &SetIteratorObjectClassOps
905 };
906
907 const JSFunctionSpec SetIteratorObject::methods[] = {
908 JS_SELF_HOSTED_FN("next", "SetIteratorNext", 0, 0),
909 JS_FS_END
910 };
911
912 static inline ValueSet::Range*
SetIteratorObjectRange(NativeObject * obj)913 SetIteratorObjectRange(NativeObject* obj)
914 {
915 MOZ_ASSERT(obj->is<SetIteratorObject>());
916 return static_cast<ValueSet::Range*>(obj->getSlot(SetIteratorObject::RangeSlot).toPrivate());
917 }
918
919 inline SetObject::IteratorKind
kind() const920 SetIteratorObject::kind() const
921 {
922 int32_t i = getSlot(KindSlot).toInt32();
923 MOZ_ASSERT(i == SetObject::Values || i == SetObject::Entries);
924 return SetObject::IteratorKind(i);
925 }
926
927 bool
initSetIteratorProto(JSContext * cx,Handle<GlobalObject * > global)928 GlobalObject::initSetIteratorProto(JSContext* cx, Handle<GlobalObject*> global)
929 {
930 Rooted<JSObject*> base(cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
931 if (!base)
932 return false;
933 RootedPlainObject proto(cx, NewObjectWithGivenProto<PlainObject>(cx, base));
934 if (!proto)
935 return false;
936 if (!JS_DefineFunctions(cx, proto, SetIteratorObject::methods) ||
937 !DefineToStringTag(cx, proto, cx->names().SetIterator))
938 {
939 return false;
940 }
941 global->setReservedSlot(SET_ITERATOR_PROTO, ObjectValue(*proto));
942 return true;
943 }
944
945 SetIteratorObject*
create(JSContext * cx,HandleObject setobj,ValueSet * data,SetObject::IteratorKind kind)946 SetIteratorObject::create(JSContext* cx, HandleObject setobj, ValueSet* data,
947 SetObject::IteratorKind kind)
948 {
949 MOZ_ASSERT(kind != SetObject::Keys);
950
951 Rooted<GlobalObject*> global(cx, &setobj->global());
952 Rooted<JSObject*> proto(cx, GlobalObject::getOrCreateSetIteratorPrototype(cx, global));
953 if (!proto)
954 return nullptr;
955
956 ValueSet::Range* range = cx->new_<ValueSet::Range>(data->all());
957 if (!range)
958 return nullptr;
959
960 SetIteratorObject* iterobj = NewObjectWithGivenProto<SetIteratorObject>(cx, proto);
961 if (!iterobj) {
962 js_delete(range);
963 return nullptr;
964 }
965 iterobj->setSlot(TargetSlot, ObjectValue(*setobj));
966 iterobj->setSlot(RangeSlot, PrivateValue(range));
967 iterobj->setSlot(KindSlot, Int32Value(int32_t(kind)));
968 return iterobj;
969 }
970
971 void
finalize(FreeOp * fop,JSObject * obj)972 SetIteratorObject::finalize(FreeOp* fop, JSObject* obj)
973 {
974 MOZ_ASSERT(fop->onMainThread());
975 fop->delete_(SetIteratorObjectRange(static_cast<NativeObject*>(obj)));
976 }
977
978 bool
next(Handle<SetIteratorObject * > setIterator,HandleArrayObject resultObj,JSContext * cx)979 SetIteratorObject::next(Handle<SetIteratorObject*> setIterator, HandleArrayObject resultObj,
980 JSContext* cx)
981 {
982 // Check invariants for inlined _GetNextSetEntryForIterator.
983
984 // The array should be tenured, so that post-barrier can be done simply.
985 MOZ_ASSERT(resultObj->isTenured());
986
987 // The array elements should be fixed.
988 MOZ_ASSERT(resultObj->hasFixedElements());
989 MOZ_ASSERT(resultObj->getDenseInitializedLength() == 1);
990 MOZ_ASSERT(resultObj->getDenseCapacity() >= 1);
991
992 ValueSet::Range* range = SetIteratorObjectRange(setIterator);
993 if (!range || range->empty()) {
994 js_delete(range);
995 setIterator->setReservedSlot(RangeSlot, PrivateValue(nullptr));
996 return true;
997 }
998 resultObj->setDenseElementWithType(cx, 0, range->front().get());
999 range->popFront();
1000 return false;
1001 }
1002
1003 /* static */ JSObject*
createResult(JSContext * cx)1004 SetIteratorObject::createResult(JSContext* cx)
1005 {
1006 RootedArrayObject resultObj(cx, NewDenseFullyAllocatedArray(cx, 1, nullptr, TenuredObject));
1007 if (!resultObj)
1008 return nullptr;
1009
1010 Rooted<TaggedProto> proto(cx, resultObj->taggedProto());
1011 ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, resultObj->getClass(), proto);
1012 if (!group)
1013 return nullptr;
1014 resultObj->setGroup(group);
1015
1016 resultObj->setDenseInitializedLength(1);
1017 resultObj->initDenseElement(0, NullValue());
1018
1019 // See comments in SetIteratorObject::next.
1020 AddTypePropertyId(cx, resultObj, JSID_VOID, TypeSet::UnknownType());
1021
1022 return resultObj;
1023 }
1024
1025
1026 /*** Set *****************************************************************************************/
1027
1028 const ClassOps SetObject::classOps_ = {
1029 nullptr, // addProperty
1030 nullptr, // delProperty
1031 nullptr, // getProperty
1032 nullptr, // setProperty
1033 nullptr, // enumerate
1034 nullptr, // resolve
1035 nullptr, // mayResolve
1036 finalize,
1037 nullptr, // call
1038 nullptr, // hasInstance
1039 nullptr, // construct
1040 mark
1041 };
1042
1043 const Class SetObject::class_ = {
1044 "Set",
1045 JSCLASS_HAS_PRIVATE |
1046 JSCLASS_HAS_RESERVED_SLOTS(SetObject::SlotCount) |
1047 JSCLASS_HAS_CACHED_PROTO(JSProto_Set) |
1048 JSCLASS_FOREGROUND_FINALIZE,
1049 &SetObject::classOps_
1050 };
1051
1052 const JSPropertySpec SetObject::properties[] = {
1053 JS_PSG("size", size, 0),
1054 JS_PS_END
1055 };
1056
1057 const JSFunctionSpec SetObject::methods[] = {
1058 JS_FN("has", has, 1, 0),
1059 JS_FN("add", add, 1, 0),
1060 JS_FN("delete", delete_, 1, 0),
1061 JS_FN("entries", entries, 0, 0),
1062 JS_FN("clear", clear, 0, 0),
1063 JS_SELF_HOSTED_FN("forEach", "SetForEach", 2, 0),
1064 JS_FS_END
1065 };
1066
1067 const JSPropertySpec SetObject::staticProperties[] = {
1068 JS_SELF_HOSTED_SYM_GET(species, "SetSpecies", 0),
1069 JS_PS_END
1070 };
1071
1072 JSObject*
initClass(JSContext * cx,JSObject * obj)1073 SetObject::initClass(JSContext* cx, JSObject* obj)
1074 {
1075 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
1076 RootedObject proto(cx,
1077 InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods,
1078 staticProperties));
1079 if (proto) {
1080 // Define the "values" method.
1081 JSFunction* fun = JS_DefineFunction(cx, proto, "values", values, 0, 0);
1082 if (!fun)
1083 return nullptr;
1084
1085 // Define its aliases.
1086 RootedValue funval(cx, ObjectValue(*fun));
1087 if (!JS_DefineProperty(cx, proto, "keys", funval, 0))
1088 return nullptr;
1089
1090 RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator));
1091 if (!JS_DefinePropertyById(cx, proto, iteratorId, funval, 0))
1092 return nullptr;
1093
1094 // Define Set.prototype[@@toStringTag].
1095 if (!DefineToStringTag(cx, proto, cx->names().Set))
1096 return nullptr;
1097 }
1098 return proto;
1099 }
1100
1101
1102 bool
keys(JSContext * cx,HandleObject obj,JS::MutableHandle<GCVector<JS::Value>> keys)1103 SetObject::keys(JSContext* cx, HandleObject obj, JS::MutableHandle<GCVector<JS::Value>> keys)
1104 {
1105 ValueSet* set = obj->as<SetObject>().getData();
1106 if (!set)
1107 return false;
1108
1109 for (ValueSet::Range r = set->all(); !r.empty(); r.popFront()) {
1110 if (!keys.append(r.front().get()))
1111 return false;
1112 }
1113
1114 return true;
1115 }
1116
1117 bool
add(JSContext * cx,HandleObject obj,HandleValue k)1118 SetObject::add(JSContext* cx, HandleObject obj, HandleValue k)
1119 {
1120 ValueSet* set = obj->as<SetObject>().getData();
1121 if (!set)
1122 return false;
1123
1124 Rooted<HashableValue> key(cx);
1125 if (!key.setValue(cx, k))
1126 return false;
1127
1128 if (!WriteBarrierPost(cx->runtime(), &obj->as<SetObject>(), key.value()) ||
1129 !set->put(key))
1130 {
1131 ReportOutOfMemory(cx);
1132 return false;
1133 }
1134 return true;
1135 }
1136
1137 SetObject*
create(JSContext * cx,HandleObject proto)1138 SetObject::create(JSContext* cx, HandleObject proto /* = nullptr */)
1139 {
1140 auto set = cx->make_unique<ValueSet>(cx->runtime(),
1141 cx->compartment()->randomHashCodeScrambler());
1142 if (!set || !set->init()) {
1143 ReportOutOfMemory(cx);
1144 return nullptr;
1145 }
1146
1147 SetObject* obj = NewObjectWithClassProto<SetObject>(cx, proto);
1148 if (!obj)
1149 return nullptr;
1150
1151 obj->setPrivate(set.release());
1152 obj->setReservedSlot(NurseryKeysSlot, PrivateValue(nullptr));
1153 return obj;
1154 }
1155
1156 void
mark(JSTracer * trc,JSObject * obj)1157 SetObject::mark(JSTracer* trc, JSObject* obj)
1158 {
1159 SetObject* setobj = static_cast<SetObject*>(obj);
1160 if (ValueSet* set = setobj->getData()) {
1161 for (ValueSet::Range r = set->all(); !r.empty(); r.popFront())
1162 MarkKey(r, r.front(), trc);
1163 }
1164 }
1165
1166 void
finalize(FreeOp * fop,JSObject * obj)1167 SetObject::finalize(FreeOp* fop, JSObject* obj)
1168 {
1169 MOZ_ASSERT(fop->onMainThread());
1170 SetObject* setobj = static_cast<SetObject*>(obj);
1171 if (ValueSet* set = setobj->getData())
1172 fop->delete_(set);
1173 }
1174
1175 bool
isBuiltinAdd(HandleValue add,JSContext * cx)1176 SetObject::isBuiltinAdd(HandleValue add, JSContext* cx)
1177 {
1178 return IsNativeFunction(add, SetObject::add);
1179 }
1180
1181 bool
construct(JSContext * cx,unsigned argc,Value * vp)1182 SetObject::construct(JSContext* cx, unsigned argc, Value* vp)
1183 {
1184 CallArgs args = CallArgsFromVp(argc, vp);
1185
1186 if (!ThrowIfNotConstructing(cx, args, "Set"))
1187 return false;
1188
1189 RootedObject proto(cx);
1190 RootedObject newTarget(cx, &args.newTarget().toObject());
1191 if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
1192 return false;
1193
1194 Rooted<SetObject*> obj(cx, SetObject::create(cx, proto));
1195 if (!obj)
1196 return false;
1197
1198 if (!args.get(0).isNullOrUndefined()) {
1199 RootedValue iterable(cx, args[0]);
1200 bool optimized = false;
1201 if (!IsOptimizableInitForSet<GlobalObject::getOrCreateSetPrototype, isBuiltinAdd>(cx, obj, iterable, &optimized))
1202 return false;
1203
1204 if (optimized) {
1205 RootedValue keyVal(cx);
1206 Rooted<HashableValue> key(cx);
1207 ValueSet* set = obj->getData();
1208 ArrayObject* array = &iterable.toObject().as<ArrayObject>();
1209 for (uint32_t index = 0; index < array->getDenseInitializedLength(); ++index) {
1210 keyVal.set(array->getDenseElement(index));
1211 MOZ_ASSERT(!keyVal.isMagic(JS_ELEMENTS_HOLE));
1212
1213 if (!key.setValue(cx, keyVal))
1214 return false;
1215 if (!WriteBarrierPost(cx->runtime(), obj, keyVal) ||
1216 !set->put(key))
1217 {
1218 ReportOutOfMemory(cx);
1219 return false;
1220 }
1221 }
1222 } else {
1223 FixedInvokeArgs<1> args2(cx);
1224 args2[0].set(args[0]);
1225
1226 RootedValue thisv(cx, ObjectValue(*obj));
1227 if (!CallSelfHostedFunction(cx, cx->names().SetConstructorInit, thisv, args2, args2.rval()))
1228 return false;
1229 }
1230 }
1231
1232 args.rval().setObject(*obj);
1233 return true;
1234 }
1235
1236 bool
is(HandleValue v)1237 SetObject::is(HandleValue v)
1238 {
1239 return v.isObject() && v.toObject().hasClass(&class_) && v.toObject().as<SetObject>().getPrivate();
1240 }
1241
1242 bool
is(HandleObject o)1243 SetObject::is(HandleObject o)
1244 {
1245 return o->hasClass(&class_) && o->as<SetObject>().getPrivate();
1246 }
1247
1248 ValueSet &
extract(HandleObject o)1249 SetObject::extract(HandleObject o)
1250 {
1251 MOZ_ASSERT(o->hasClass(&SetObject::class_));
1252 return *o->as<SetObject>().getData();
1253 }
1254
1255 ValueSet &
extract(const CallArgs & args)1256 SetObject::extract(const CallArgs& args)
1257 {
1258 MOZ_ASSERT(args.thisv().isObject());
1259 MOZ_ASSERT(args.thisv().toObject().hasClass(&SetObject::class_));
1260 return *static_cast<SetObject&>(args.thisv().toObject()).getData();
1261 }
1262
1263 uint32_t
size(JSContext * cx,HandleObject obj)1264 SetObject::size(JSContext *cx, HandleObject obj)
1265 {
1266 MOZ_ASSERT(SetObject::is(obj));
1267 ValueSet &set = extract(obj);
1268 static_assert(sizeof(set.count()) <= sizeof(uint32_t),
1269 "set count must be precisely representable as a JS number");
1270 return set.count();
1271 }
1272
1273 bool
size_impl(JSContext * cx,const CallArgs & args)1274 SetObject::size_impl(JSContext* cx, const CallArgs& args)
1275 {
1276 MOZ_ASSERT(is(args.thisv()));
1277
1278 ValueSet& set = extract(args);
1279 static_assert(sizeof(set.count()) <= sizeof(uint32_t),
1280 "set count must be precisely representable as a JS number");
1281 args.rval().setNumber(set.count());
1282 return true;
1283 }
1284
1285 bool
size(JSContext * cx,unsigned argc,Value * vp)1286 SetObject::size(JSContext* cx, unsigned argc, Value* vp)
1287 {
1288 CallArgs args = CallArgsFromVp(argc, vp);
1289 return CallNonGenericMethod<SetObject::is, SetObject::size_impl>(cx, args);
1290 }
1291
1292 bool
has_impl(JSContext * cx,const CallArgs & args)1293 SetObject::has_impl(JSContext* cx, const CallArgs& args)
1294 {
1295 MOZ_ASSERT(is(args.thisv()));
1296
1297 ValueSet& set = extract(args);
1298 ARG0_KEY(cx, args, key);
1299 args.rval().setBoolean(set.has(key));
1300 return true;
1301 }
1302
1303 bool
has(JSContext * cx,HandleObject obj,HandleValue key,bool * rval)1304 SetObject::has(JSContext *cx, HandleObject obj, HandleValue key, bool *rval)
1305 {
1306 MOZ_ASSERT(SetObject::is(obj));
1307
1308 ValueSet &set = extract(obj);
1309 Rooted<HashableValue> k(cx);
1310
1311 if (!k.setValue(cx, key))
1312 return false;
1313
1314 *rval = set.has(k);
1315 return true;
1316 }
1317
1318 bool
has(JSContext * cx,unsigned argc,Value * vp)1319 SetObject::has(JSContext *cx, unsigned argc, Value *vp)
1320 {
1321 CallArgs args = CallArgsFromVp(argc, vp);
1322 return CallNonGenericMethod<SetObject::is, SetObject::has_impl>(cx, args);
1323 }
1324
1325 bool
add_impl(JSContext * cx,const CallArgs & args)1326 SetObject::add_impl(JSContext* cx, const CallArgs& args)
1327 {
1328 MOZ_ASSERT(is(args.thisv()));
1329
1330 ValueSet& set = extract(args);
1331 ARG0_KEY(cx, args, key);
1332 if (!WriteBarrierPost(cx->runtime(), &args.thisv().toObject().as<SetObject>(), key.value()) ||
1333 !set.put(key))
1334 {
1335 ReportOutOfMemory(cx);
1336 return false;
1337 }
1338 args.rval().set(args.thisv());
1339 return true;
1340 }
1341
1342 bool
add(JSContext * cx,unsigned argc,Value * vp)1343 SetObject::add(JSContext* cx, unsigned argc, Value* vp)
1344 {
1345 CallArgs args = CallArgsFromVp(argc, vp);
1346 return CallNonGenericMethod<SetObject::is, SetObject::add_impl>(cx, args);
1347 }
1348
1349 bool
delete_(JSContext * cx,HandleObject obj,HandleValue key,bool * rval)1350 SetObject::delete_(JSContext *cx, HandleObject obj, HandleValue key, bool *rval)
1351 {
1352 MOZ_ASSERT(SetObject::is(obj));
1353
1354 ValueSet &set = extract(obj);
1355 Rooted<HashableValue> k(cx);
1356
1357 if (!k.setValue(cx, key))
1358 return false;
1359
1360 if (!set.remove(k, rval)) {
1361 ReportOutOfMemory(cx);
1362 return false;
1363 }
1364 return true;
1365 }
1366
1367 bool
delete_impl(JSContext * cx,const CallArgs & args)1368 SetObject::delete_impl(JSContext *cx, const CallArgs& args)
1369 {
1370 MOZ_ASSERT(is(args.thisv()));
1371
1372 ValueSet& set = extract(args);
1373 ARG0_KEY(cx, args, key);
1374 bool found;
1375 if (!set.remove(key, &found)) {
1376 ReportOutOfMemory(cx);
1377 return false;
1378 }
1379 args.rval().setBoolean(found);
1380 return true;
1381 }
1382
1383 bool
delete_(JSContext * cx,unsigned argc,Value * vp)1384 SetObject::delete_(JSContext* cx, unsigned argc, Value* vp)
1385 {
1386 CallArgs args = CallArgsFromVp(argc, vp);
1387 return CallNonGenericMethod<SetObject::is, SetObject::delete_impl>(cx, args);
1388 }
1389
1390 bool
iterator(JSContext * cx,IteratorKind kind,HandleObject obj,MutableHandleValue iter)1391 SetObject::iterator(JSContext *cx, IteratorKind kind,
1392 HandleObject obj, MutableHandleValue iter)
1393 {
1394 MOZ_ASSERT(SetObject::is(obj));
1395 ValueSet &set = extract(obj);
1396 Rooted<JSObject*> iterobj(cx, SetIteratorObject::create(cx, obj, &set, kind));
1397 return iterobj && (iter.setObject(*iterobj), true);
1398 }
1399
1400 bool
iterator_impl(JSContext * cx,const CallArgs & args,IteratorKind kind)1401 SetObject::iterator_impl(JSContext *cx, const CallArgs& args, IteratorKind kind)
1402 {
1403 Rooted<SetObject*> setobj(cx, &args.thisv().toObject().as<SetObject>());
1404 ValueSet& set = *setobj->getData();
1405 Rooted<JSObject*> iterobj(cx, SetIteratorObject::create(cx, setobj, &set, kind));
1406 if (!iterobj)
1407 return false;
1408 args.rval().setObject(*iterobj);
1409 return true;
1410 }
1411
1412 bool
values_impl(JSContext * cx,const CallArgs & args)1413 SetObject::values_impl(JSContext* cx, const CallArgs& args)
1414 {
1415 return iterator_impl(cx, args, Values);
1416 }
1417
1418 bool
values(JSContext * cx,unsigned argc,Value * vp)1419 SetObject::values(JSContext* cx, unsigned argc, Value* vp)
1420 {
1421 CallArgs args = CallArgsFromVp(argc, vp);
1422 return CallNonGenericMethod(cx, is, values_impl, args);
1423 }
1424
1425 bool
entries_impl(JSContext * cx,const CallArgs & args)1426 SetObject::entries_impl(JSContext* cx, const CallArgs& args)
1427 {
1428 return iterator_impl(cx, args, Entries);
1429 }
1430
1431 bool
entries(JSContext * cx,unsigned argc,Value * vp)1432 SetObject::entries(JSContext* cx, unsigned argc, Value* vp)
1433 {
1434 CallArgs args = CallArgsFromVp(argc, vp);
1435 return CallNonGenericMethod(cx, is, entries_impl, args);
1436 }
1437
1438 bool
clear(JSContext * cx,HandleObject obj)1439 SetObject::clear(JSContext *cx, HandleObject obj)
1440 {
1441 MOZ_ASSERT(SetObject::is(obj));
1442 ValueSet &set = extract(obj);
1443 if (!set.clear()) {
1444 ReportOutOfMemory(cx);
1445 return false;
1446 }
1447 return true;
1448 }
1449
1450 bool
clear_impl(JSContext * cx,const CallArgs & args)1451 SetObject::clear_impl(JSContext *cx, const CallArgs& args)
1452 {
1453 Rooted<SetObject*> setobj(cx, &args.thisv().toObject().as<SetObject>());
1454 if (!setobj->getData()->clear()) {
1455 ReportOutOfMemory(cx);
1456 return false;
1457 }
1458 args.rval().setUndefined();
1459 return true;
1460 }
1461
1462 bool
clear(JSContext * cx,unsigned argc,Value * vp)1463 SetObject::clear(JSContext* cx, unsigned argc, Value* vp)
1464 {
1465 CallArgs args = CallArgsFromVp(argc, vp);
1466 return CallNonGenericMethod(cx, is, clear_impl, args);
1467 }
1468
1469 JSObject*
InitSetClass(JSContext * cx,HandleObject obj)1470 js::InitSetClass(JSContext* cx, HandleObject obj)
1471 {
1472 return SetObject::initClass(cx, obj);
1473 }
1474
1475 /*** JS static utility functions *********************************************/
1476
1477 static bool
forEach(const char * funcName,JSContext * cx,HandleObject obj,HandleValue callbackFn,HandleValue thisArg)1478 forEach(const char* funcName, JSContext *cx, HandleObject obj, HandleValue callbackFn, HandleValue thisArg)
1479 {
1480 CHECK_REQUEST(cx);
1481
1482 RootedId forEachId(cx, NameToId(cx->names().forEach));
1483 RootedFunction forEachFunc(cx, JS::GetSelfHostedFunction(cx, funcName, forEachId, 2));
1484 if (!forEachFunc)
1485 return false;
1486
1487 RootedValue fval(cx, ObjectValue(*forEachFunc));
1488 return Call(cx, fval, obj, callbackFn, thisArg, &fval);
1489 }
1490
1491 // Handles Clear/Size for public jsapi map/set access
1492 template<typename RetT>
1493 RetT
CallObjFunc(RetT (* ObjFunc)(JSContext *,HandleObject),JSContext * cx,HandleObject obj)1494 CallObjFunc(RetT(*ObjFunc)(JSContext*, HandleObject), JSContext* cx, HandleObject obj)
1495 {
1496 CHECK_REQUEST(cx);
1497 assertSameCompartment(cx, obj);
1498
1499 // Always unwrap, in case this is an xray or cross-compartment wrapper.
1500 RootedObject unwrappedObj(cx);
1501 unwrappedObj = UncheckedUnwrap(obj);
1502
1503 // Enter the compartment of the backing object before calling functions on
1504 // it.
1505 JSAutoCompartment ac(cx, unwrappedObj);
1506 return ObjFunc(cx, unwrappedObj);
1507 }
1508
1509 // Handles Has/Delete for public jsapi map/set access
1510 bool
CallObjFunc(bool (* ObjFunc)(JSContext * cx,HandleObject obj,HandleValue key,bool * rval),JSContext * cx,HandleObject obj,HandleValue key,bool * rval)1511 CallObjFunc(bool(*ObjFunc)(JSContext *cx, HandleObject obj, HandleValue key, bool *rval),
1512 JSContext *cx, HandleObject obj, HandleValue key, bool *rval)
1513 {
1514 CHECK_REQUEST(cx);
1515 assertSameCompartment(cx, obj, key);
1516
1517 // Always unwrap, in case this is an xray or cross-compartment wrapper.
1518 RootedObject unwrappedObj(cx);
1519 unwrappedObj = UncheckedUnwrap(obj);
1520 JSAutoCompartment ac(cx, unwrappedObj);
1521
1522 // If we're working with a wrapped map/set, rewrap the key into the
1523 // compartment of the unwrapped map/set.
1524 RootedValue wrappedKey(cx, key);
1525 if (obj != unwrappedObj) {
1526 if (!JS_WrapValue(cx, &wrappedKey))
1527 return false;
1528 }
1529 return ObjFunc(cx, unwrappedObj, wrappedKey, rval);
1530 }
1531
1532 // Handles iterator generation for public jsapi map/set access
1533 template<typename Iter>
1534 bool
CallObjFunc(bool (* ObjFunc)(JSContext * cx,Iter kind,HandleObject obj,MutableHandleValue iter),JSContext * cx,Iter iterType,HandleObject obj,MutableHandleValue rval)1535 CallObjFunc(bool(*ObjFunc)(JSContext* cx, Iter kind,
1536 HandleObject obj, MutableHandleValue iter),
1537 JSContext *cx, Iter iterType, HandleObject obj, MutableHandleValue rval)
1538 {
1539 CHECK_REQUEST(cx);
1540 assertSameCompartment(cx, obj);
1541
1542 // Always unwrap, in case this is an xray or cross-compartment wrapper.
1543 RootedObject unwrappedObj(cx);
1544 unwrappedObj = UncheckedUnwrap(obj);
1545 {
1546 // Retrieve the iterator while in the unwrapped map/set's compartment,
1547 // otherwise we'll crash on a compartment assert.
1548 JSAutoCompartment ac(cx, unwrappedObj);
1549 if (!ObjFunc(cx, iterType, unwrappedObj, rval))
1550 return false;
1551 }
1552
1553 // If the caller is in a different compartment than the map/set, rewrap the
1554 // iterator object into the caller's compartment.
1555 if (obj != unwrappedObj) {
1556 if (!JS_WrapValue(cx, rval))
1557 return false;
1558 }
1559 return true;
1560 }
1561
1562 /*** JS public APIs **********************************************************/
1563
JS_PUBLIC_API(JSObject *)1564 JS_PUBLIC_API(JSObject*)
1565 JS::NewMapObject(JSContext* cx)
1566 {
1567 return MapObject::create(cx);
1568 }
1569
JS_PUBLIC_API(uint32_t)1570 JS_PUBLIC_API(uint32_t)
1571 JS::MapSize(JSContext* cx, HandleObject obj)
1572 {
1573 return CallObjFunc<uint32_t>(&MapObject::size, cx, obj);
1574 }
1575
JS_PUBLIC_API(bool)1576 JS_PUBLIC_API(bool)
1577 JS::MapGet(JSContext* cx, HandleObject obj, HandleValue key, MutableHandleValue rval)
1578 {
1579 CHECK_REQUEST(cx);
1580 assertSameCompartment(cx, obj, key, rval);
1581
1582 // Unwrap the object, and enter its compartment. If object isn't wrapped,
1583 // this is essentially a noop.
1584 RootedObject unwrappedObj(cx);
1585 unwrappedObj = UncheckedUnwrap(obj);
1586 {
1587 JSAutoCompartment ac(cx, unwrappedObj);
1588 RootedValue wrappedKey(cx, key);
1589
1590 // If we passed in a wrapper, wrap our key into its compartment now.
1591 if (obj != unwrappedObj) {
1592 if (!JS_WrapValue(cx, &wrappedKey))
1593 return false;
1594 }
1595 if (!MapObject::get(cx, unwrappedObj, wrappedKey, rval))
1596 return false;
1597 }
1598
1599 // If we passed in a wrapper, wrap our return value on the way out.
1600 if (obj != unwrappedObj) {
1601 if (!JS_WrapValue(cx, rval))
1602 return false;
1603 }
1604 return true;
1605 }
1606
JS_PUBLIC_API(bool)1607 JS_PUBLIC_API(bool)
1608 JS::MapSet(JSContext *cx, HandleObject obj, HandleValue key, HandleValue val)
1609 {
1610 CHECK_REQUEST(cx);
1611 assertSameCompartment(cx, obj, key, val);
1612
1613 // Unwrap the object, and enter its compartment. If object isn't wrapped,
1614 // this is essentially a noop.
1615 RootedObject unwrappedObj(cx);
1616 unwrappedObj = UncheckedUnwrap(obj);
1617 {
1618 JSAutoCompartment ac(cx, unwrappedObj);
1619
1620 // If we passed in a wrapper, wrap both key and value before adding to
1621 // the map
1622 RootedValue wrappedKey(cx, key);
1623 RootedValue wrappedValue(cx, val);
1624 if (obj != unwrappedObj) {
1625 if (!JS_WrapValue(cx, &wrappedKey) ||
1626 !JS_WrapValue(cx, &wrappedValue)) {
1627 return false;
1628 }
1629 }
1630 return MapObject::set(cx, unwrappedObj, wrappedKey, wrappedValue);
1631 }
1632 }
1633
JS_PUBLIC_API(bool)1634 JS_PUBLIC_API(bool)
1635 JS::MapHas(JSContext* cx, HandleObject obj, HandleValue key, bool* rval)
1636 {
1637 return CallObjFunc(MapObject::has, cx, obj, key, rval);
1638 }
1639
JS_PUBLIC_API(bool)1640 JS_PUBLIC_API(bool)
1641 JS::MapDelete(JSContext *cx, HandleObject obj, HandleValue key, bool* rval)
1642 {
1643 return CallObjFunc(MapObject::delete_, cx, obj, key, rval);
1644 }
1645
JS_PUBLIC_API(bool)1646 JS_PUBLIC_API(bool)
1647 JS::MapClear(JSContext* cx, HandleObject obj)
1648 {
1649 return CallObjFunc(&MapObject::clear, cx, obj);
1650 }
1651
JS_PUBLIC_API(bool)1652 JS_PUBLIC_API(bool)
1653 JS::MapKeys(JSContext* cx, HandleObject obj, MutableHandleValue rval)
1654 {
1655 return CallObjFunc(&MapObject::iterator, cx, MapObject::Keys, obj, rval);
1656 }
1657
JS_PUBLIC_API(bool)1658 JS_PUBLIC_API(bool)
1659 JS::MapValues(JSContext* cx, HandleObject obj, MutableHandleValue rval)
1660 {
1661 return CallObjFunc(&MapObject::iterator, cx, MapObject::Values, obj, rval);
1662 }
1663
JS_PUBLIC_API(bool)1664 JS_PUBLIC_API(bool)
1665 JS::MapEntries(JSContext* cx, HandleObject obj, MutableHandleValue rval)
1666 {
1667 return CallObjFunc(&MapObject::iterator, cx, MapObject::Entries, obj, rval);
1668 }
1669
JS_PUBLIC_API(bool)1670 JS_PUBLIC_API(bool)
1671 JS::MapForEach(JSContext *cx, HandleObject obj, HandleValue callbackFn, HandleValue thisVal)
1672 {
1673 return forEach("MapForEach", cx, obj, callbackFn, thisVal);
1674 }
1675
JS_PUBLIC_API(JSObject *)1676 JS_PUBLIC_API(JSObject *)
1677 JS::NewSetObject(JSContext *cx)
1678 {
1679 return SetObject::create(cx);
1680 }
1681
JS_PUBLIC_API(uint32_t)1682 JS_PUBLIC_API(uint32_t)
1683 JS::SetSize(JSContext *cx, HandleObject obj)
1684 {
1685 return CallObjFunc<uint32_t>(&SetObject::size, cx, obj);
1686 }
1687
JS_PUBLIC_API(bool)1688 JS_PUBLIC_API(bool)
1689 JS::SetAdd(JSContext *cx, HandleObject obj, HandleValue key)
1690 {
1691 CHECK_REQUEST(cx);
1692 assertSameCompartment(cx, obj, key);
1693
1694 // Unwrap the object, and enter its compartment. If object isn't wrapped,
1695 // this is essentially a noop.
1696 RootedObject unwrappedObj(cx);
1697 unwrappedObj = UncheckedUnwrap(obj);
1698 {
1699 JSAutoCompartment ac(cx, unwrappedObj);
1700
1701 // If we passed in a wrapper, wrap key before adding to the set
1702 RootedValue wrappedKey(cx, key);
1703 if (obj != unwrappedObj) {
1704 if (!JS_WrapValue(cx, &wrappedKey))
1705 return false;
1706 }
1707 return SetObject::add(cx, unwrappedObj, wrappedKey);
1708 }
1709 }
1710
JS_PUBLIC_API(bool)1711 JS_PUBLIC_API(bool)
1712 JS::SetHas(JSContext* cx, HandleObject obj, HandleValue key, bool* rval)
1713 {
1714 return CallObjFunc(SetObject::has, cx, obj, key, rval);
1715 }
1716
JS_PUBLIC_API(bool)1717 JS_PUBLIC_API(bool)
1718 JS::SetDelete(JSContext *cx, HandleObject obj, HandleValue key, bool *rval)
1719 {
1720 return CallObjFunc(SetObject::delete_, cx, obj, key, rval);
1721 }
1722
JS_PUBLIC_API(bool)1723 JS_PUBLIC_API(bool)
1724 JS::SetClear(JSContext* cx, HandleObject obj)
1725 {
1726 return CallObjFunc(&SetObject::clear, cx, obj);
1727 }
1728
JS_PUBLIC_API(bool)1729 JS_PUBLIC_API(bool)
1730 JS::SetKeys(JSContext* cx, HandleObject obj, MutableHandleValue rval)
1731 {
1732 return SetValues(cx, obj, rval);
1733 }
1734
JS_PUBLIC_API(bool)1735 JS_PUBLIC_API(bool)
1736 JS::SetValues(JSContext* cx, HandleObject obj, MutableHandleValue rval)
1737 {
1738 return CallObjFunc(&SetObject::iterator, cx, SetObject::Values, obj, rval);
1739 }
1740
JS_PUBLIC_API(bool)1741 JS_PUBLIC_API(bool)
1742 JS::SetEntries(JSContext* cx, HandleObject obj, MutableHandleValue rval)
1743 {
1744 return CallObjFunc(&SetObject::iterator, cx, SetObject::Entries, obj, rval);
1745 }
1746
JS_PUBLIC_API(bool)1747 JS_PUBLIC_API(bool)
1748 JS::SetForEach(JSContext *cx, HandleObject obj, HandleValue callbackFn, HandleValue thisVal)
1749 {
1750 return forEach("SetForEach", cx, obj, callbackFn, thisVal);
1751 }
1752