Source code

Revision control

Other Tools

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