diff options
Diffstat (limited to 'js/src/builtin/MapObject.cpp')
-rw-r--r-- | js/src/builtin/MapObject.cpp | 1751 |
1 files changed, 1751 insertions, 0 deletions
diff --git a/js/src/builtin/MapObject.cpp b/js/src/builtin/MapObject.cpp new file mode 100644 index 000000000..c496cfb77 --- /dev/null +++ b/js/src/builtin/MapObject.cpp @@ -0,0 +1,1751 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "builtin/MapObject.h" + +#include "jscntxt.h" +#include "jsiter.h" +#include "jsobj.h" + +#include "ds/OrderedHashTable.h" +#include "gc/Marking.h" +#include "js/Utility.h" +#include "vm/GlobalObject.h" +#include "vm/Interpreter.h" +#include "vm/SelfHosting.h" +#include "vm/Symbol.h" + +#include "jsobjinlines.h" + +#include "vm/Interpreter-inl.h" +#include "vm/NativeObject-inl.h" + +using namespace js; + +using mozilla::ArrayLength; +using mozilla::IsNaN; +using mozilla::NumberEqualsInt32; + +using JS::DoubleNaNValue; +using JS::ForOfIterator; + + +/*** HashableValue *******************************************************************************/ + +bool +HashableValue::setValue(JSContext* cx, HandleValue v) +{ + if (v.isString()) { + // Atomize so that hash() and operator==() are fast and infallible. + JSString* str = AtomizeString(cx, v.toString(), DoNotPinAtom); + if (!str) + return false; + value = StringValue(str); + } else if (v.isDouble()) { + double d = v.toDouble(); + int32_t i; + if (NumberEqualsInt32(d, &i)) { + // Normalize int32_t-valued doubles to int32_t for faster hashing and testing. + value = Int32Value(i); + } else if (IsNaN(d)) { + // NaNs with different bits must hash and test identically. + value = DoubleNaNValue(); + } else { + value = v; + } + } else { + value = v; + } + + MOZ_ASSERT(value.isUndefined() || value.isNull() || value.isBoolean() || value.isNumber() || + value.isString() || value.isSymbol() || value.isObject()); + return true; +} + +static HashNumber +HashValue(const Value& v, const mozilla::HashCodeScrambler& hcs) +{ + // HashableValue::setValue normalizes values so that the SameValue relation + // on HashableValues is the same as the == relationship on + // value.asRawBits(). So why not just return that? Security. + // + // To avoid revealing GC of atoms, string-based hash codes are computed + // from the string contents rather than any pointer; to avoid revealing + // addresses, pointer-based hash codes are computed using the + // HashCodeScrambler. + + if (v.isString()) + return v.toString()->asAtom().hash(); + if (v.isSymbol()) + return v.toSymbol()->hash(); + if (v.isObject()) + return hcs.scramble(v.asRawBits()); + + MOZ_ASSERT(v.isNull() || !v.isGCThing(), "do not reveal pointers via hash codes"); + return v.asRawBits(); +} + +HashNumber +HashableValue::hash(const mozilla::HashCodeScrambler& hcs) const +{ + return HashValue(value, hcs); +} + +bool +HashableValue::operator==(const HashableValue& other) const +{ + // Two HashableValues are equal if they have equal bits. + bool b = (value.asRawBits() == other.value.asRawBits()); + +#ifdef DEBUG + bool same; + JS::RootingContext* rcx = GetJSContextFromMainThread(); + RootedValue valueRoot(rcx, value); + RootedValue otherRoot(rcx, other.value); + MOZ_ASSERT(SameValue(nullptr, valueRoot, otherRoot, &same)); + MOZ_ASSERT(same == b); +#endif + return b; +} + +HashableValue +HashableValue::mark(JSTracer* trc) const +{ + HashableValue hv(*this); + TraceEdge(trc, &hv.value, "key"); + return hv; +} + + +/*** MapIterator *********************************************************************************/ + +namespace { + +} /* anonymous namespace */ + +static const ClassOps MapIteratorObjectClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + MapIteratorObject::finalize +}; + +const Class MapIteratorObject::class_ = { + "Map Iterator", + JSCLASS_HAS_RESERVED_SLOTS(MapIteratorObject::SlotCount) | + JSCLASS_FOREGROUND_FINALIZE, + &MapIteratorObjectClassOps +}; + +const JSFunctionSpec MapIteratorObject::methods[] = { + JS_SELF_HOSTED_FN("next", "MapIteratorNext", 0, 0), + JS_FS_END +}; + +static inline ValueMap::Range* +MapIteratorObjectRange(NativeObject* obj) +{ + MOZ_ASSERT(obj->is<MapIteratorObject>()); + return static_cast<ValueMap::Range*>(obj->getSlot(MapIteratorObject::RangeSlot).toPrivate()); +} + +inline MapObject::IteratorKind +MapIteratorObject::kind() const +{ + int32_t i = getSlot(KindSlot).toInt32(); + MOZ_ASSERT(i == MapObject::Keys || i == MapObject::Values || i == MapObject::Entries); + return MapObject::IteratorKind(i); +} + +bool +GlobalObject::initMapIteratorProto(JSContext* cx, Handle<GlobalObject*> global) +{ + Rooted<JSObject*> base(cx, GlobalObject::getOrCreateIteratorPrototype(cx, global)); + if (!base) + return false; + RootedPlainObject proto(cx, NewObjectWithGivenProto<PlainObject>(cx, base)); + if (!proto) + return false; + if (!JS_DefineFunctions(cx, proto, MapIteratorObject::methods) || + !DefineToStringTag(cx, proto, cx->names().MapIterator)) + { + return false; + } + global->setReservedSlot(MAP_ITERATOR_PROTO, ObjectValue(*proto)); + return true; +} + +MapIteratorObject* +MapIteratorObject::create(JSContext* cx, HandleObject mapobj, ValueMap* data, + MapObject::IteratorKind kind) +{ + Rooted<GlobalObject*> global(cx, &mapobj->global()); + Rooted<JSObject*> proto(cx, GlobalObject::getOrCreateMapIteratorPrototype(cx, global)); + if (!proto) + return nullptr; + + ValueMap::Range* range = cx->new_<ValueMap::Range>(data->all()); + if (!range) + return nullptr; + + MapIteratorObject* iterobj = NewObjectWithGivenProto<MapIteratorObject>(cx, proto); + if (!iterobj) { + js_delete(range); + return nullptr; + } + iterobj->setSlot(TargetSlot, ObjectValue(*mapobj)); + iterobj->setSlot(RangeSlot, PrivateValue(range)); + iterobj->setSlot(KindSlot, Int32Value(int32_t(kind))); + return iterobj; +} + +void +MapIteratorObject::finalize(FreeOp* fop, JSObject* obj) +{ + MOZ_ASSERT(fop->onMainThread()); + fop->delete_(MapIteratorObjectRange(static_cast<NativeObject*>(obj))); +} + +bool +MapIteratorObject::next(Handle<MapIteratorObject*> mapIterator, HandleArrayObject resultPairObj, + JSContext* cx) +{ + // Check invariants for inlined _GetNextMapEntryForIterator. + + // The array should be tenured, so that post-barrier can be done simply. + MOZ_ASSERT(resultPairObj->isTenured()); + + // The array elements should be fixed. + MOZ_ASSERT(resultPairObj->hasFixedElements()); + MOZ_ASSERT(resultPairObj->getDenseInitializedLength() == 2); + MOZ_ASSERT(resultPairObj->getDenseCapacity() >= 2); + + ValueMap::Range* range = MapIteratorObjectRange(mapIterator); + if (!range || range->empty()) { + js_delete(range); + mapIterator->setReservedSlot(RangeSlot, PrivateValue(nullptr)); + return true; + } + switch (mapIterator->kind()) { + case MapObject::Keys: + resultPairObj->setDenseElementWithType(cx, 0, range->front().key.get()); + break; + + case MapObject::Values: + resultPairObj->setDenseElementWithType(cx, 1, range->front().value); + break; + + case MapObject::Entries: { + resultPairObj->setDenseElementWithType(cx, 0, range->front().key.get()); + resultPairObj->setDenseElementWithType(cx, 1, range->front().value); + break; + } + } + range->popFront(); + return false; +} + +/* static */ JSObject* +MapIteratorObject::createResultPair(JSContext* cx) +{ + RootedArrayObject resultPairObj(cx, NewDenseFullyAllocatedArray(cx, 2, nullptr, TenuredObject)); + if (!resultPairObj) + return nullptr; + + Rooted<TaggedProto> proto(cx, resultPairObj->taggedProto()); + ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, resultPairObj->getClass(), proto); + if (!group) + return nullptr; + resultPairObj->setGroup(group); + + resultPairObj->setDenseInitializedLength(2); + resultPairObj->initDenseElement(0, NullValue()); + resultPairObj->initDenseElement(1, NullValue()); + + // See comments in MapIteratorObject::next. + AddTypePropertyId(cx, resultPairObj, JSID_VOID, TypeSet::UnknownType()); + + return resultPairObj; +} + + +/*** Map *****************************************************************************************/ + +const ClassOps MapObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // getProperty + nullptr, // setProperty + nullptr, // enumerate + nullptr, // resolve + nullptr, // mayResolve + finalize, + nullptr, // call + nullptr, // hasInstance + nullptr, // construct + mark +}; + +const Class MapObject::class_ = { + "Map", + JSCLASS_HAS_PRIVATE | + JSCLASS_HAS_RESERVED_SLOTS(MapObject::SlotCount) | + JSCLASS_HAS_CACHED_PROTO(JSProto_Map) | + JSCLASS_FOREGROUND_FINALIZE, + &MapObject::classOps_ +}; + +const JSPropertySpec MapObject::properties[] = { + JS_PSG("size", size, 0), + JS_PS_END +}; + +const JSFunctionSpec MapObject::methods[] = { + JS_FN("get", get, 1, 0), + JS_FN("has", has, 1, 0), + JS_FN("set", set, 2, 0), + JS_FN("delete", delete_, 1, 0), + JS_FN("keys", keys, 0, 0), + JS_FN("values", values, 0, 0), + JS_FN("clear", clear, 0, 0), + JS_SELF_HOSTED_FN("forEach", "MapForEach", 2, 0), + JS_FS_END +}; + +const JSPropertySpec MapObject::staticProperties[] = { + JS_SELF_HOSTED_SYM_GET(species, "MapSpecies", 0), + JS_PS_END +}; + +static JSObject* +InitClass(JSContext* cx, Handle<GlobalObject*> global, const Class* clasp, JSProtoKey key, Native construct, + const JSPropertySpec* properties, const JSFunctionSpec* methods, + const JSPropertySpec* staticProperties) +{ + RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx)); + if (!proto) + return nullptr; + + Rooted<JSFunction*> ctor(cx, global->createConstructor(cx, construct, ClassName(key, cx), 0)); + if (!ctor || + !JS_DefineProperties(cx, ctor, staticProperties) || + !LinkConstructorAndPrototype(cx, ctor, proto) || + !DefinePropertiesAndFunctions(cx, proto, properties, methods) || + !GlobalObject::initBuiltinConstructor(cx, global, key, ctor, proto)) + { + return nullptr; + } + return proto; +} + +JSObject* +MapObject::initClass(JSContext* cx, JSObject* obj) +{ + Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); + RootedObject proto(cx, + InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods, + staticProperties)); + if (proto) { + // Define the "entries" method. + JSFunction* fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0); + if (!fun) + return nullptr; + + // Define its alias. + RootedValue funval(cx, ObjectValue(*fun)); + RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator)); + if (!JS_DefinePropertyById(cx, proto, iteratorId, funval, 0)) + return nullptr; + + // Define Map.prototype[@@toStringTag]. + if (!DefineToStringTag(cx, proto, cx->names().Map)) + return nullptr; + } + return proto; +} + +template <class Range> +static void +MarkKey(Range& r, const HashableValue& key, JSTracer* trc) +{ + HashableValue newKey = key.mark(trc); + + if (newKey.get() != key.get()) { + // The hash function only uses the bits of the Value, so it is safe to + // rekey even when the object or string has been modified by the GC. + r.rekeyFront(newKey); + } +} + +void +MapObject::mark(JSTracer* trc, JSObject* obj) +{ + if (ValueMap* map = obj->as<MapObject>().getData()) { + for (ValueMap::Range r = map->all(); !r.empty(); r.popFront()) { + MarkKey(r, r.front().key, trc); + TraceEdge(trc, &r.front().value, "value"); + } + } +} + +struct js::UnbarrieredHashPolicy { + typedef Value Lookup; + static HashNumber hash(const Lookup& v, const mozilla::HashCodeScrambler& hcs) { + return HashValue(v, hcs); + } + static bool match(const Value& k, const Lookup& l) { return k == l; } + static bool isEmpty(const Value& v) { return v.isMagic(JS_HASH_KEY_EMPTY); } + static void makeEmpty(Value* vp) { vp->setMagic(JS_HASH_KEY_EMPTY); } +}; + +using NurseryKeysVector = Vector<JSObject*, 0, SystemAllocPolicy>; + +template <typename TableObject> +static NurseryKeysVector* +GetNurseryKeys(TableObject* t) +{ + Value value = t->getReservedSlot(TableObject::NurseryKeysSlot); + return reinterpret_cast<NurseryKeysVector*>(value.toPrivate()); +} + +template <typename TableObject> +static NurseryKeysVector* +AllocNurseryKeys(TableObject* t) +{ + MOZ_ASSERT(!GetNurseryKeys(t)); + auto keys = js_new<NurseryKeysVector>(); + if (!keys) + return nullptr; + + t->setReservedSlot(TableObject::NurseryKeysSlot, PrivateValue(keys)); + return keys; +} + +template <typename TableObject> +static void +DeleteNurseryKeys(TableObject* t) +{ + auto keys = GetNurseryKeys(t); + MOZ_ASSERT(keys); + js_delete(keys); + t->setReservedSlot(TableObject::NurseryKeysSlot, PrivateValue(nullptr)); +} + +// A generic store buffer entry that traces all nursery keys for an ordered hash +// map or set. +template <typename ObjectT> +class js::OrderedHashTableRef : public gc::BufferableRef +{ + ObjectT* object; + + public: + explicit OrderedHashTableRef(ObjectT* obj) : object(obj) {} + + void trace(JSTracer* trc) override { + auto realTable = object->getData(); + auto unbarrieredTable = reinterpret_cast<typename ObjectT::UnbarrieredTable*>(realTable); + NurseryKeysVector* keys = GetNurseryKeys(object); + MOZ_ASSERT(keys); + for (JSObject* obj : *keys) { + MOZ_ASSERT(obj); + Value key = ObjectValue(*obj); + Value prior = key; + MOZ_ASSERT(unbarrieredTable->hash(key) == + realTable->hash(*reinterpret_cast<HashableValue*>(&key))); + TraceManuallyBarrieredEdge(trc, &key, "ordered hash table key"); + unbarrieredTable->rekeyOneEntry(prior, key); + } + DeleteNurseryKeys(object); + } +}; + +template <typename ObjectT> +inline static MOZ_MUST_USE bool +WriteBarrierPostImpl(JSRuntime* rt, ObjectT* obj, const Value& keyValue) +{ + if (MOZ_LIKELY(!keyValue.isObject())) + return true; + + JSObject* key = &keyValue.toObject(); + if (!IsInsideNursery(key)) + return true; + + NurseryKeysVector* keys = GetNurseryKeys(obj); + if (!keys) { + keys = AllocNurseryKeys(obj); + if (!keys) + return false; + + rt->gc.storeBuffer.putGeneric(OrderedHashTableRef<ObjectT>(obj)); + } + + if (!keys->append(key)) + return false; + + return true; +} + +inline static MOZ_MUST_USE bool +WriteBarrierPost(JSRuntime* rt, MapObject* map, const Value& key) +{ + return WriteBarrierPostImpl(rt, map, key); +} + +inline static MOZ_MUST_USE bool +WriteBarrierPost(JSRuntime* rt, SetObject* set, const Value& key) +{ + return WriteBarrierPostImpl(rt, set, key); +} + +bool +MapObject::getKeysAndValuesInterleaved(JSContext* cx, HandleObject obj, + JS::MutableHandle<GCVector<JS::Value>> entries) +{ + ValueMap* map = obj->as<MapObject>().getData(); + if (!map) + return false; + + for (ValueMap::Range r = map->all(); !r.empty(); r.popFront()) { + if (!entries.append(r.front().key.get()) || + !entries.append(r.front().value)) + { + return false; + } + } + + return true; +} + +bool +MapObject::set(JSContext* cx, HandleObject obj, HandleValue k, HandleValue v) +{ + ValueMap* map = obj->as<MapObject>().getData(); + if (!map) + return false; + + Rooted<HashableValue> key(cx); + if (!key.setValue(cx, k)) + return false; + + HeapPtr<Value> rval(v); + if (!WriteBarrierPost(cx->runtime(), &obj->as<MapObject>(), key.value()) || + !map->put(key, rval)) + { + ReportOutOfMemory(cx); + return false; + } + + return true; +} + +MapObject* +MapObject::create(JSContext* cx, HandleObject proto /* = nullptr */) +{ + auto map = cx->make_unique<ValueMap>(cx->runtime(), + cx->compartment()->randomHashCodeScrambler()); + if (!map || !map->init()) { + ReportOutOfMemory(cx); + return nullptr; + } + + MapObject* mapObj = NewObjectWithClassProto<MapObject>(cx, proto); + if (!mapObj) + return nullptr; + + mapObj->setPrivate(map.release()); + mapObj->setReservedSlot(NurseryKeysSlot, PrivateValue(nullptr)); + return mapObj; +} + +void +MapObject::finalize(FreeOp* fop, JSObject* obj) +{ + MOZ_ASSERT(fop->onMainThread()); + if (ValueMap* map = obj->as<MapObject>().getData()) + fop->delete_(map); +} + +bool +MapObject::construct(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "Map")) + return false; + + RootedObject proto(cx); + RootedObject newTarget(cx, &args.newTarget().toObject()); + if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) + return false; + + Rooted<MapObject*> obj(cx, MapObject::create(cx, proto)); + if (!obj) + return false; + + if (!args.get(0).isNullOrUndefined()) { + FixedInvokeArgs<1> args2(cx); + args2[0].set(args[0]); + + RootedValue thisv(cx, ObjectValue(*obj)); + if (!CallSelfHostedFunction(cx, cx->names().MapConstructorInit, thisv, args2, args2.rval())) + return false; + } + + args.rval().setObject(*obj); + return true; +} + +bool +MapObject::is(HandleValue v) +{ + return v.isObject() && v.toObject().hasClass(&class_) && v.toObject().as<MapObject>().getPrivate(); +} + +bool +MapObject::is(HandleObject o) +{ + return o->hasClass(&class_) && o->as<MapObject>().getPrivate(); +} + +#define ARG0_KEY(cx, args, key) \ + Rooted<HashableValue> key(cx); \ + if (args.length() > 0 && !key.setValue(cx, args[0])) \ + return false + +ValueMap& +MapObject::extract(HandleObject o) +{ + MOZ_ASSERT(o->hasClass(&MapObject::class_)); + return *o->as<MapObject>().getData(); +} + +ValueMap& +MapObject::extract(const CallArgs& args) +{ + MOZ_ASSERT(args.thisv().isObject()); + MOZ_ASSERT(args.thisv().toObject().hasClass(&MapObject::class_)); + return *args.thisv().toObject().as<MapObject>().getData(); +} + +uint32_t +MapObject::size(JSContext* cx, HandleObject obj) +{ + ValueMap& map = extract(obj); + static_assert(sizeof(map.count()) <= sizeof(uint32_t), + "map count must be precisely representable as a JS number"); + return map.count(); +} + +bool +MapObject::size_impl(JSContext* cx, const CallArgs& args) +{ + RootedObject obj(cx, &args.thisv().toObject()); + args.rval().setNumber(size(cx, obj)); + return true; +} + +bool +MapObject::size(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<MapObject::is, MapObject::size_impl>(cx, args); +} + +bool +MapObject::get(JSContext* cx, HandleObject obj, + HandleValue key, MutableHandleValue rval) +{ + ValueMap& map = extract(obj); + Rooted<HashableValue> k(cx); + + if (!k.setValue(cx, key)) + return false; + + if (ValueMap::Entry* p = map.get(k)) + rval.set(p->value); + else + rval.setUndefined(); + + return true; +} + +bool +MapObject::get_impl(JSContext* cx, const CallArgs& args) +{ + RootedObject obj(cx, &args.thisv().toObject()); + return get(cx, obj, args.get(0), args.rval()); +} + +bool +MapObject::get(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<MapObject::is, MapObject::get_impl>(cx, args); +} + +bool +MapObject::has(JSContext* cx, HandleObject obj, HandleValue key, bool* rval) +{ + ValueMap& map = extract(obj); + Rooted<HashableValue> k(cx); + + if (!k.setValue(cx, key)) + return false; + + *rval = map.has(k); + return true; +} + +bool +MapObject::has_impl(JSContext* cx, const CallArgs& args) +{ + bool found; + RootedObject obj(cx, &args.thisv().toObject()); + if (has(cx, obj, args.get(0), &found)) { + args.rval().setBoolean(found); + return true; + } + return false; +} + +bool +MapObject::has(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<MapObject::is, MapObject::has_impl>(cx, args); +} + +bool +MapObject::set_impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(MapObject::is(args.thisv())); + + ValueMap& map = extract(args); + ARG0_KEY(cx, args, key); + HeapPtr<Value> rval(args.get(1)); + if (!WriteBarrierPost(cx->runtime(), &args.thisv().toObject().as<MapObject>(), key.value()) || + !map.put(key, rval)) + { + ReportOutOfMemory(cx); + return false; + } + + args.rval().set(args.thisv()); + return true; +} + +bool +MapObject::set(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<MapObject::is, MapObject::set_impl>(cx, args); +} + +bool +MapObject::delete_(JSContext *cx, HandleObject obj, HandleValue key, bool *rval) +{ + ValueMap &map = extract(obj); + Rooted<HashableValue> k(cx); + + if (!k.setValue(cx, key)) + return false; + + if (!map.remove(k, rval)) { + ReportOutOfMemory(cx); + return false; + } + return true; +} + +bool +MapObject::delete_impl(JSContext *cx, const CallArgs& args) +{ + // MapObject::mark does not mark deleted entries. Incremental GC therefore + // requires that no HeapPtr<Value> objects pointing to heap values be left + // alive in the ValueMap. + // + // OrderedHashMap::remove() doesn't destroy the removed entry. It merely + // calls OrderedHashMap::MapOps::makeEmpty. But that is sufficient, because + // makeEmpty clears the value by doing e->value = Value(), and in the case + // of a ValueMap, Value() means HeapPtr<Value>(), which is the same as + // HeapPtr<Value>(UndefinedValue()). + MOZ_ASSERT(MapObject::is(args.thisv())); + + ValueMap& map = extract(args); + ARG0_KEY(cx, args, key); + bool found; + if (!map.remove(key, &found)) { + ReportOutOfMemory(cx); + return false; + } + args.rval().setBoolean(found); + return true; +} + +bool +MapObject::delete_(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<MapObject::is, MapObject::delete_impl>(cx, args); +} + +bool +MapObject::iterator(JSContext* cx, IteratorKind kind, + HandleObject obj, MutableHandleValue iter) +{ + ValueMap& map = extract(obj); + Rooted<JSObject*> iterobj(cx, MapIteratorObject::create(cx, obj, &map, kind)); + return iterobj && (iter.setObject(*iterobj), true); +} + +bool +MapObject::iterator_impl(JSContext* cx, const CallArgs& args, IteratorKind kind) +{ + RootedObject obj(cx, &args.thisv().toObject()); + return iterator(cx, kind, obj, args.rval()); +} + +bool +MapObject::keys_impl(JSContext* cx, const CallArgs& args) +{ + return iterator_impl(cx, args, Keys); +} + +bool +MapObject::keys(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, is, keys_impl, args); +} + +bool +MapObject::values_impl(JSContext* cx, const CallArgs& args) +{ + return iterator_impl(cx, args, Values); +} + +bool +MapObject::values(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, is, values_impl, args); +} + +bool +MapObject::entries_impl(JSContext* cx, const CallArgs& args) +{ + return iterator_impl(cx, args, Entries); +} + +bool +MapObject::entries(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, is, entries_impl, args); +} + +bool +MapObject::clear_impl(JSContext* cx, const CallArgs& args) +{ + RootedObject obj(cx, &args.thisv().toObject()); + args.rval().setUndefined(); + return clear(cx, obj); +} + +bool +MapObject::clear(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, is, clear_impl, args); +} + +bool +MapObject::clear(JSContext* cx, HandleObject obj) +{ + ValueMap& map = extract(obj); + if (!map.clear()) { + ReportOutOfMemory(cx); + return false; + } + return true; +} + +JSObject* +js::InitMapClass(JSContext* cx, HandleObject obj) +{ + return MapObject::initClass(cx, obj); +} + + +/*** SetIterator *********************************************************************************/ + +static const ClassOps SetIteratorObjectClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + SetIteratorObject::finalize +}; + +const Class SetIteratorObject::class_ = { + "Set Iterator", + JSCLASS_HAS_RESERVED_SLOTS(SetIteratorObject::SlotCount) | + JSCLASS_FOREGROUND_FINALIZE, + &SetIteratorObjectClassOps +}; + +const JSFunctionSpec SetIteratorObject::methods[] = { + JS_SELF_HOSTED_FN("next", "SetIteratorNext", 0, 0), + JS_FS_END +}; + +static inline ValueSet::Range* +SetIteratorObjectRange(NativeObject* obj) +{ + MOZ_ASSERT(obj->is<SetIteratorObject>()); + return static_cast<ValueSet::Range*>(obj->getSlot(SetIteratorObject::RangeSlot).toPrivate()); +} + +inline SetObject::IteratorKind +SetIteratorObject::kind() const +{ + int32_t i = getSlot(KindSlot).toInt32(); + MOZ_ASSERT(i == SetObject::Values || i == SetObject::Entries); + return SetObject::IteratorKind(i); +} + +bool +GlobalObject::initSetIteratorProto(JSContext* cx, Handle<GlobalObject*> global) +{ + Rooted<JSObject*> base(cx, GlobalObject::getOrCreateIteratorPrototype(cx, global)); + if (!base) + return false; + RootedPlainObject proto(cx, NewObjectWithGivenProto<PlainObject>(cx, base)); + if (!proto) + return false; + if (!JS_DefineFunctions(cx, proto, SetIteratorObject::methods) || + !DefineToStringTag(cx, proto, cx->names().SetIterator)) + { + return false; + } + global->setReservedSlot(SET_ITERATOR_PROTO, ObjectValue(*proto)); + return true; +} + +SetIteratorObject* +SetIteratorObject::create(JSContext* cx, HandleObject setobj, ValueSet* data, + SetObject::IteratorKind kind) +{ + MOZ_ASSERT(kind != SetObject::Keys); + + Rooted<GlobalObject*> global(cx, &setobj->global()); + Rooted<JSObject*> proto(cx, GlobalObject::getOrCreateSetIteratorPrototype(cx, global)); + if (!proto) + return nullptr; + + ValueSet::Range* range = cx->new_<ValueSet::Range>(data->all()); + if (!range) + return nullptr; + + SetIteratorObject* iterobj = NewObjectWithGivenProto<SetIteratorObject>(cx, proto); + if (!iterobj) { + js_delete(range); + return nullptr; + } + iterobj->setSlot(TargetSlot, ObjectValue(*setobj)); + iterobj->setSlot(RangeSlot, PrivateValue(range)); + iterobj->setSlot(KindSlot, Int32Value(int32_t(kind))); + return iterobj; +} + +void +SetIteratorObject::finalize(FreeOp* fop, JSObject* obj) +{ + MOZ_ASSERT(fop->onMainThread()); + fop->delete_(SetIteratorObjectRange(static_cast<NativeObject*>(obj))); +} + +bool +SetIteratorObject::next(Handle<SetIteratorObject*> setIterator, HandleArrayObject resultObj, + JSContext* cx) +{ + // Check invariants for inlined _GetNextSetEntryForIterator. + + // The array should be tenured, so that post-barrier can be done simply. + MOZ_ASSERT(resultObj->isTenured()); + + // The array elements should be fixed. + MOZ_ASSERT(resultObj->hasFixedElements()); + MOZ_ASSERT(resultObj->getDenseInitializedLength() == 1); + MOZ_ASSERT(resultObj->getDenseCapacity() >= 1); + + ValueSet::Range* range = SetIteratorObjectRange(setIterator); + if (!range || range->empty()) { + js_delete(range); + setIterator->setReservedSlot(RangeSlot, PrivateValue(nullptr)); + return true; + } + resultObj->setDenseElementWithType(cx, 0, range->front().get()); + range->popFront(); + return false; +} + +/* static */ JSObject* +SetIteratorObject::createResult(JSContext* cx) +{ + RootedArrayObject resultObj(cx, NewDenseFullyAllocatedArray(cx, 1, nullptr, TenuredObject)); + if (!resultObj) + return nullptr; + + Rooted<TaggedProto> proto(cx, resultObj->taggedProto()); + ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, resultObj->getClass(), proto); + if (!group) + return nullptr; + resultObj->setGroup(group); + + resultObj->setDenseInitializedLength(1); + resultObj->initDenseElement(0, NullValue()); + + // See comments in SetIteratorObject::next. + AddTypePropertyId(cx, resultObj, JSID_VOID, TypeSet::UnknownType()); + + return resultObj; +} + + +/*** Set *****************************************************************************************/ + +const ClassOps SetObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // getProperty + nullptr, // setProperty + nullptr, // enumerate + nullptr, // resolve + nullptr, // mayResolve + finalize, + nullptr, // call + nullptr, // hasInstance + nullptr, // construct + mark +}; + +const Class SetObject::class_ = { + "Set", + JSCLASS_HAS_PRIVATE | + JSCLASS_HAS_RESERVED_SLOTS(SetObject::SlotCount) | + JSCLASS_HAS_CACHED_PROTO(JSProto_Set) | + JSCLASS_FOREGROUND_FINALIZE, + &SetObject::classOps_ +}; + +const JSPropertySpec SetObject::properties[] = { + JS_PSG("size", size, 0), + JS_PS_END +}; + +const JSFunctionSpec SetObject::methods[] = { + JS_FN("has", has, 1, 0), + JS_FN("add", add, 1, 0), + JS_FN("delete", delete_, 1, 0), + JS_FN("entries", entries, 0, 0), + JS_FN("clear", clear, 0, 0), + JS_SELF_HOSTED_FN("forEach", "SetForEach", 2, 0), + JS_FS_END +}; + +const JSPropertySpec SetObject::staticProperties[] = { + JS_SELF_HOSTED_SYM_GET(species, "SetSpecies", 0), + JS_PS_END +}; + +JSObject* +SetObject::initClass(JSContext* cx, JSObject* obj) +{ + Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); + RootedObject proto(cx, + InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods, + staticProperties)); + if (proto) { + // Define the "values" method. + JSFunction* fun = JS_DefineFunction(cx, proto, "values", values, 0, 0); + if (!fun) + return nullptr; + + // Define its aliases. + RootedValue funval(cx, ObjectValue(*fun)); + if (!JS_DefineProperty(cx, proto, "keys", funval, 0)) + return nullptr; + + RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator)); + if (!JS_DefinePropertyById(cx, proto, iteratorId, funval, 0)) + return nullptr; + + // Define Set.prototype[@@toStringTag]. + if (!DefineToStringTag(cx, proto, cx->names().Set)) + return nullptr; + } + return proto; +} + + +bool +SetObject::keys(JSContext* cx, HandleObject obj, JS::MutableHandle<GCVector<JS::Value>> keys) +{ + ValueSet* set = obj->as<SetObject>().getData(); + if (!set) + return false; + + for (ValueSet::Range r = set->all(); !r.empty(); r.popFront()) { + if (!keys.append(r.front().get())) + return false; + } + + return true; +} + +bool +SetObject::add(JSContext* cx, HandleObject obj, HandleValue k) +{ + ValueSet* set = obj->as<SetObject>().getData(); + if (!set) + return false; + + Rooted<HashableValue> key(cx); + if (!key.setValue(cx, k)) + return false; + + if (!WriteBarrierPost(cx->runtime(), &obj->as<SetObject>(), key.value()) || + !set->put(key)) + { + ReportOutOfMemory(cx); + return false; + } + return true; +} + +SetObject* +SetObject::create(JSContext* cx, HandleObject proto /* = nullptr */) +{ + auto set = cx->make_unique<ValueSet>(cx->runtime(), + cx->compartment()->randomHashCodeScrambler()); + if (!set || !set->init()) { + ReportOutOfMemory(cx); + return nullptr; + } + + SetObject* obj = NewObjectWithClassProto<SetObject>(cx, proto); + if (!obj) + return nullptr; + + obj->setPrivate(set.release()); + obj->setReservedSlot(NurseryKeysSlot, PrivateValue(nullptr)); + return obj; +} + +void +SetObject::mark(JSTracer* trc, JSObject* obj) +{ + SetObject* setobj = static_cast<SetObject*>(obj); + if (ValueSet* set = setobj->getData()) { + for (ValueSet::Range r = set->all(); !r.empty(); r.popFront()) + MarkKey(r, r.front(), trc); + } +} + +void +SetObject::finalize(FreeOp* fop, JSObject* obj) +{ + MOZ_ASSERT(fop->onMainThread()); + SetObject* setobj = static_cast<SetObject*>(obj); + if (ValueSet* set = setobj->getData()) + fop->delete_(set); +} + +bool +SetObject::isBuiltinAdd(HandleValue add, JSContext* cx) +{ + return IsNativeFunction(add, SetObject::add); +} + +bool +SetObject::construct(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "Set")) + return false; + + RootedObject proto(cx); + RootedObject newTarget(cx, &args.newTarget().toObject()); + if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) + return false; + + Rooted<SetObject*> obj(cx, SetObject::create(cx, proto)); + if (!obj) + return false; + + if (!args.get(0).isNullOrUndefined()) { + RootedValue iterable(cx, args[0]); + bool optimized = false; + if (!IsOptimizableInitForSet<GlobalObject::getOrCreateSetPrototype, isBuiltinAdd>(cx, obj, iterable, &optimized)) + return false; + + if (optimized) { + RootedValue keyVal(cx); + Rooted<HashableValue> key(cx); + ValueSet* set = obj->getData(); + ArrayObject* array = &iterable.toObject().as<ArrayObject>(); + for (uint32_t index = 0; index < array->getDenseInitializedLength(); ++index) { + keyVal.set(array->getDenseElement(index)); + MOZ_ASSERT(!keyVal.isMagic(JS_ELEMENTS_HOLE)); + + if (!key.setValue(cx, keyVal)) + return false; + if (!WriteBarrierPost(cx->runtime(), obj, keyVal) || + !set->put(key)) + { + ReportOutOfMemory(cx); + return false; + } + } + } else { + FixedInvokeArgs<1> args2(cx); + args2[0].set(args[0]); + + RootedValue thisv(cx, ObjectValue(*obj)); + if (!CallSelfHostedFunction(cx, cx->names().SetConstructorInit, thisv, args2, args2.rval())) + return false; + } + } + + args.rval().setObject(*obj); + return true; +} + +bool +SetObject::is(HandleValue v) +{ + return v.isObject() && v.toObject().hasClass(&class_) && v.toObject().as<SetObject>().getPrivate(); +} + +bool +SetObject::is(HandleObject o) +{ + return o->hasClass(&class_) && o->as<SetObject>().getPrivate(); +} + +ValueSet & +SetObject::extract(HandleObject o) +{ + MOZ_ASSERT(o->hasClass(&SetObject::class_)); + return *o->as<SetObject>().getData(); +} + +ValueSet & +SetObject::extract(const CallArgs& args) +{ + MOZ_ASSERT(args.thisv().isObject()); + MOZ_ASSERT(args.thisv().toObject().hasClass(&SetObject::class_)); + return *static_cast<SetObject&>(args.thisv().toObject()).getData(); +} + +uint32_t +SetObject::size(JSContext *cx, HandleObject obj) +{ + MOZ_ASSERT(SetObject::is(obj)); + ValueSet &set = extract(obj); + static_assert(sizeof(set.count()) <= sizeof(uint32_t), + "set count must be precisely representable as a JS number"); + return set.count(); +} + +bool +SetObject::size_impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(is(args.thisv())); + + ValueSet& set = extract(args); + static_assert(sizeof(set.count()) <= sizeof(uint32_t), + "set count must be precisely representable as a JS number"); + args.rval().setNumber(set.count()); + return true; +} + +bool +SetObject::size(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<SetObject::is, SetObject::size_impl>(cx, args); +} + +bool +SetObject::has_impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(is(args.thisv())); + + ValueSet& set = extract(args); + ARG0_KEY(cx, args, key); + args.rval().setBoolean(set.has(key)); + return true; +} + +bool +SetObject::has(JSContext *cx, HandleObject obj, HandleValue key, bool *rval) +{ + MOZ_ASSERT(SetObject::is(obj)); + + ValueSet &set = extract(obj); + Rooted<HashableValue> k(cx); + + if (!k.setValue(cx, key)) + return false; + + *rval = set.has(k); + return true; +} + +bool +SetObject::has(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<SetObject::is, SetObject::has_impl>(cx, args); +} + +bool +SetObject::add_impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(is(args.thisv())); + + ValueSet& set = extract(args); + ARG0_KEY(cx, args, key); + if (!WriteBarrierPost(cx->runtime(), &args.thisv().toObject().as<SetObject>(), key.value()) || + !set.put(key)) + { + ReportOutOfMemory(cx); + return false; + } + args.rval().set(args.thisv()); + return true; +} + +bool +SetObject::add(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<SetObject::is, SetObject::add_impl>(cx, args); +} + +bool +SetObject::delete_(JSContext *cx, HandleObject obj, HandleValue key, bool *rval) +{ + MOZ_ASSERT(SetObject::is(obj)); + + ValueSet &set = extract(obj); + Rooted<HashableValue> k(cx); + + if (!k.setValue(cx, key)) + return false; + + if (!set.remove(k, rval)) { + ReportOutOfMemory(cx); + return false; + } + return true; +} + +bool +SetObject::delete_impl(JSContext *cx, const CallArgs& args) +{ + MOZ_ASSERT(is(args.thisv())); + + ValueSet& set = extract(args); + ARG0_KEY(cx, args, key); + bool found; + if (!set.remove(key, &found)) { + ReportOutOfMemory(cx); + return false; + } + args.rval().setBoolean(found); + return true; +} + +bool +SetObject::delete_(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<SetObject::is, SetObject::delete_impl>(cx, args); +} + +bool +SetObject::iterator(JSContext *cx, IteratorKind kind, + HandleObject obj, MutableHandleValue iter) +{ + MOZ_ASSERT(SetObject::is(obj)); + ValueSet &set = extract(obj); + Rooted<JSObject*> iterobj(cx, SetIteratorObject::create(cx, obj, &set, kind)); + return iterobj && (iter.setObject(*iterobj), true); +} + +bool +SetObject::iterator_impl(JSContext *cx, const CallArgs& args, IteratorKind kind) +{ + Rooted<SetObject*> setobj(cx, &args.thisv().toObject().as<SetObject>()); + ValueSet& set = *setobj->getData(); + Rooted<JSObject*> iterobj(cx, SetIteratorObject::create(cx, setobj, &set, kind)); + if (!iterobj) + return false; + args.rval().setObject(*iterobj); + return true; +} + +bool +SetObject::values_impl(JSContext* cx, const CallArgs& args) +{ + return iterator_impl(cx, args, Values); +} + +bool +SetObject::values(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, is, values_impl, args); +} + +bool +SetObject::entries_impl(JSContext* cx, const CallArgs& args) +{ + return iterator_impl(cx, args, Entries); +} + +bool +SetObject::entries(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, is, entries_impl, args); +} + +bool +SetObject::clear(JSContext *cx, HandleObject obj) +{ + MOZ_ASSERT(SetObject::is(obj)); + ValueSet &set = extract(obj); + if (!set.clear()) { + ReportOutOfMemory(cx); + return false; + } + return true; +} + +bool +SetObject::clear_impl(JSContext *cx, const CallArgs& args) +{ + Rooted<SetObject*> setobj(cx, &args.thisv().toObject().as<SetObject>()); + if (!setobj->getData()->clear()) { + ReportOutOfMemory(cx); + return false; + } + args.rval().setUndefined(); + return true; +} + +bool +SetObject::clear(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, is, clear_impl, args); +} + +JSObject* +js::InitSetClass(JSContext* cx, HandleObject obj) +{ + return SetObject::initClass(cx, obj); +} + +/*** JS static utility functions *********************************************/ + +static bool +forEach(const char* funcName, JSContext *cx, HandleObject obj, HandleValue callbackFn, HandleValue thisArg) +{ + CHECK_REQUEST(cx); + + RootedId forEachId(cx, NameToId(cx->names().forEach)); + RootedFunction forEachFunc(cx, JS::GetSelfHostedFunction(cx, funcName, forEachId, 2)); + if (!forEachFunc) + return false; + + RootedValue fval(cx, ObjectValue(*forEachFunc)); + return Call(cx, fval, obj, callbackFn, thisArg, &fval); +} + +// Handles Clear/Size for public jsapi map/set access +template<typename RetT> +RetT +CallObjFunc(RetT(*ObjFunc)(JSContext*, HandleObject), JSContext* cx, HandleObject obj) +{ + CHECK_REQUEST(cx); + assertSameCompartment(cx, obj); + + // Always unwrap, in case this is an xray or cross-compartment wrapper. + RootedObject unwrappedObj(cx); + unwrappedObj = UncheckedUnwrap(obj); + + // Enter the compartment of the backing object before calling functions on + // it. + JSAutoCompartment ac(cx, unwrappedObj); + return ObjFunc(cx, unwrappedObj); +} + +// Handles Has/Delete for public jsapi map/set access +bool +CallObjFunc(bool(*ObjFunc)(JSContext *cx, HandleObject obj, HandleValue key, bool *rval), + JSContext *cx, HandleObject obj, HandleValue key, bool *rval) +{ + CHECK_REQUEST(cx); + assertSameCompartment(cx, obj, key); + + // Always unwrap, in case this is an xray or cross-compartment wrapper. + RootedObject unwrappedObj(cx); + unwrappedObj = UncheckedUnwrap(obj); + JSAutoCompartment ac(cx, unwrappedObj); + + // If we're working with a wrapped map/set, rewrap the key into the + // compartment of the unwrapped map/set. + RootedValue wrappedKey(cx, key); + if (obj != unwrappedObj) { + if (!JS_WrapValue(cx, &wrappedKey)) + return false; + } + return ObjFunc(cx, unwrappedObj, wrappedKey, rval); +} + +// Handles iterator generation for public jsapi map/set access +template<typename Iter> +bool +CallObjFunc(bool(*ObjFunc)(JSContext* cx, Iter kind, + HandleObject obj, MutableHandleValue iter), + JSContext *cx, Iter iterType, HandleObject obj, MutableHandleValue rval) +{ + CHECK_REQUEST(cx); + assertSameCompartment(cx, obj); + + // Always unwrap, in case this is an xray or cross-compartment wrapper. + RootedObject unwrappedObj(cx); + unwrappedObj = UncheckedUnwrap(obj); + { + // Retrieve the iterator while in the unwrapped map/set's compartment, + // otherwise we'll crash on a compartment assert. + JSAutoCompartment ac(cx, unwrappedObj); + if (!ObjFunc(cx, iterType, unwrappedObj, rval)) + return false; + } + + // If the caller is in a different compartment than the map/set, rewrap the + // iterator object into the caller's compartment. + if (obj != unwrappedObj) { + if (!JS_WrapValue(cx, rval)) + return false; + } + return true; +} + +/*** JS public APIs **********************************************************/ + +JS_PUBLIC_API(JSObject*) +JS::NewMapObject(JSContext* cx) +{ + return MapObject::create(cx); +} + +JS_PUBLIC_API(uint32_t) +JS::MapSize(JSContext* cx, HandleObject obj) +{ + return CallObjFunc<uint32_t>(&MapObject::size, cx, obj); +} + +JS_PUBLIC_API(bool) +JS::MapGet(JSContext* cx, HandleObject obj, HandleValue key, MutableHandleValue rval) +{ + CHECK_REQUEST(cx); + assertSameCompartment(cx, obj, key, rval); + + // Unwrap the object, and enter its compartment. If object isn't wrapped, + // this is essentially a noop. + RootedObject unwrappedObj(cx); + unwrappedObj = UncheckedUnwrap(obj); + { + JSAutoCompartment ac(cx, unwrappedObj); + RootedValue wrappedKey(cx, key); + + // If we passed in a wrapper, wrap our key into its compartment now. + if (obj != unwrappedObj) { + if (!JS_WrapValue(cx, &wrappedKey)) + return false; + } + if (!MapObject::get(cx, unwrappedObj, wrappedKey, rval)) + return false; + } + + // If we passed in a wrapper, wrap our return value on the way out. + if (obj != unwrappedObj) { + if (!JS_WrapValue(cx, rval)) + return false; + } + return true; +} + +JS_PUBLIC_API(bool) +JS::MapSet(JSContext *cx, HandleObject obj, HandleValue key, HandleValue val) +{ + CHECK_REQUEST(cx); + assertSameCompartment(cx, obj, key, val); + + // Unwrap the object, and enter its compartment. If object isn't wrapped, + // this is essentially a noop. + RootedObject unwrappedObj(cx); + unwrappedObj = UncheckedUnwrap(obj); + { + JSAutoCompartment ac(cx, unwrappedObj); + + // If we passed in a wrapper, wrap both key and value before adding to + // the map + RootedValue wrappedKey(cx, key); + RootedValue wrappedValue(cx, val); + if (obj != unwrappedObj) { + if (!JS_WrapValue(cx, &wrappedKey) || + !JS_WrapValue(cx, &wrappedValue)) { + return false; + } + } + return MapObject::set(cx, unwrappedObj, wrappedKey, wrappedValue); + } +} + +JS_PUBLIC_API(bool) +JS::MapHas(JSContext* cx, HandleObject obj, HandleValue key, bool* rval) +{ + return CallObjFunc(MapObject::has, cx, obj, key, rval); +} + +JS_PUBLIC_API(bool) +JS::MapDelete(JSContext *cx, HandleObject obj, HandleValue key, bool* rval) +{ + return CallObjFunc(MapObject::delete_, cx, obj, key, rval); +} + +JS_PUBLIC_API(bool) +JS::MapClear(JSContext* cx, HandleObject obj) +{ + return CallObjFunc(&MapObject::clear, cx, obj); +} + +JS_PUBLIC_API(bool) +JS::MapKeys(JSContext* cx, HandleObject obj, MutableHandleValue rval) +{ + return CallObjFunc(&MapObject::iterator, cx, MapObject::Keys, obj, rval); +} + +JS_PUBLIC_API(bool) +JS::MapValues(JSContext* cx, HandleObject obj, MutableHandleValue rval) +{ + return CallObjFunc(&MapObject::iterator, cx, MapObject::Values, obj, rval); +} + +JS_PUBLIC_API(bool) +JS::MapEntries(JSContext* cx, HandleObject obj, MutableHandleValue rval) +{ + return CallObjFunc(&MapObject::iterator, cx, MapObject::Entries, obj, rval); +} + +JS_PUBLIC_API(bool) +JS::MapForEach(JSContext *cx, HandleObject obj, HandleValue callbackFn, HandleValue thisVal) +{ + return forEach("MapForEach", cx, obj, callbackFn, thisVal); +} + +JS_PUBLIC_API(JSObject *) +JS::NewSetObject(JSContext *cx) +{ + return SetObject::create(cx); +} + +JS_PUBLIC_API(uint32_t) +JS::SetSize(JSContext *cx, HandleObject obj) +{ + return CallObjFunc<uint32_t>(&SetObject::size, cx, obj); +} + +JS_PUBLIC_API(bool) +JS::SetAdd(JSContext *cx, HandleObject obj, HandleValue key) +{ + CHECK_REQUEST(cx); + assertSameCompartment(cx, obj, key); + + // Unwrap the object, and enter its compartment. If object isn't wrapped, + // this is essentially a noop. + RootedObject unwrappedObj(cx); + unwrappedObj = UncheckedUnwrap(obj); + { + JSAutoCompartment ac(cx, unwrappedObj); + + // If we passed in a wrapper, wrap key before adding to the set + RootedValue wrappedKey(cx, key); + if (obj != unwrappedObj) { + if (!JS_WrapValue(cx, &wrappedKey)) + return false; + } + return SetObject::add(cx, unwrappedObj, wrappedKey); + } +} + +JS_PUBLIC_API(bool) +JS::SetHas(JSContext* cx, HandleObject obj, HandleValue key, bool* rval) +{ + return CallObjFunc(SetObject::has, cx, obj, key, rval); +} + +JS_PUBLIC_API(bool) +JS::SetDelete(JSContext *cx, HandleObject obj, HandleValue key, bool *rval) +{ + return CallObjFunc(SetObject::delete_, cx, obj, key, rval); +} + +JS_PUBLIC_API(bool) +JS::SetClear(JSContext* cx, HandleObject obj) +{ + return CallObjFunc(&SetObject::clear, cx, obj); +} + +JS_PUBLIC_API(bool) +JS::SetKeys(JSContext* cx, HandleObject obj, MutableHandleValue rval) +{ + return SetValues(cx, obj, rval); +} + +JS_PUBLIC_API(bool) +JS::SetValues(JSContext* cx, HandleObject obj, MutableHandleValue rval) +{ + return CallObjFunc(&SetObject::iterator, cx, SetObject::Values, obj, rval); +} + +JS_PUBLIC_API(bool) +JS::SetEntries(JSContext* cx, HandleObject obj, MutableHandleValue rval) +{ + return CallObjFunc(&SetObject::iterator, cx, SetObject::Entries, obj, rval); +} + +JS_PUBLIC_API(bool) +JS::SetForEach(JSContext *cx, HandleObject obj, HandleValue callbackFn, HandleValue thisVal) +{ + return forEach("SetForEach", cx, obj, callbackFn, thisVal); +} |