1739 lines
47 KiB
C++
1739 lines
47 KiB
C++
/* -*- 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/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(0)
|
||
// Remove this code (M1312001/M1330769). The exploit cannot succeed on PowerPC
|
||
// and this code has a performance impact.
|
||
if (v.isObject())
|
||
return hcs.scramble(v.asRawBits());
|
||
|
||
MOZ_ASSERT(v.isNull() || !v.isGCThing(), "do not reveal pointers via hash codes");
|
||
#endif
|
||
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;
|
||
PerThreadData* data = TlsPerThreadData.get();
|
||
RootedValue valueRoot(data, value);
|
||
RootedValue otherRoot(data, 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 */
|
||
|
||
const Class MapIteratorObject::class_ = {
|
||
"Map Iterator",
|
||
JSCLASS_HAS_RESERVED_SLOTS(MapIteratorObject::SlotCount),
|
||
nullptr, /* addProperty */
|
||
nullptr, /* delProperty */
|
||
nullptr, /* getProperty */
|
||
nullptr, /* setProperty */
|
||
nullptr, /* enumerate */
|
||
nullptr, /* resolve */
|
||
nullptr, /* mayResolve */
|
||
MapIteratorObject::finalize
|
||
};
|
||
|
||
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)
|
||
{
|
||
fop->delete_(MapIteratorObjectRange(static_cast<NativeObject*>(obj)));
|
||
}
|
||
|
||
bool
|
||
MapIteratorObject::next(JSContext* cx, Handle<MapIteratorObject*> mapIterator,
|
||
HandleArrayObject resultPairObj)
|
||
{
|
||
MOZ_ASSERT(resultPairObj->getDenseInitializedLength() == 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;
|
||
}
|
||
|
||
|
||
/*** Map *****************************************************************************************/
|
||
|
||
const Class MapObject::class_ = {
|
||
"Map",
|
||
JSCLASS_HAS_PRIVATE |
|
||
JSCLASS_HAS_CACHED_PROTO(JSProto_Map),
|
||
nullptr, // addProperty
|
||
nullptr, // delProperty
|
||
nullptr, // getProperty
|
||
nullptr, // setProperty
|
||
nullptr, // enumerate
|
||
nullptr, // resolve
|
||
nullptr, // mayResolve
|
||
finalize,
|
||
nullptr, // call
|
||
nullptr, // hasInstance
|
||
nullptr, // construct
|
||
mark
|
||
};
|
||
|
||
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 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); }
|
||
};
|
||
|
||
template <typename RealTableType, typename TableType>
|
||
class OrderedHashTableRef : public gc::BufferableRef
|
||
{
|
||
TableType* table;
|
||
Value key;
|
||
|
||
public:
|
||
explicit OrderedHashTableRef(TableType* t, const Value& k) : table(t), key(k) {}
|
||
|
||
void trace(JSTracer* trc) override {
|
||
MOZ_ASSERT(reinterpret_cast<RealTableType*>(table)
|
||
->hash(*reinterpret_cast<HashableValue*>(&key)) ==
|
||
table->hash(key));
|
||
Value prior = key;
|
||
TraceManuallyBarrieredEdge(trc, &key, "ordered hash table key");
|
||
table->rekeyOneEntry(prior, key);
|
||
}
|
||
};
|
||
|
||
inline static void
|
||
WriteBarrierPost(JSRuntime* rt, ValueMap* map, const Value& key)
|
||
{
|
||
typedef OrderedHashMap<Value, Value, UnbarrieredHashPolicy, RuntimeAllocPolicy> UnbarrieredMap;
|
||
if (MOZ_UNLIKELY(key.isObject() && IsInsideNursery(&key.toObject()))) {
|
||
rt->gc.storeBuffer.putGeneric(OrderedHashTableRef<ValueMap, UnbarrieredMap>(
|
||
reinterpret_cast<UnbarrieredMap*>(map), key));
|
||
}
|
||
}
|
||
|
||
inline static void
|
||
WriteBarrierPost(JSRuntime* rt, ValueSet* set, const Value& key)
|
||
{
|
||
typedef OrderedHashSet<Value, UnbarrieredHashPolicy, RuntimeAllocPolicy> UnbarrieredSet;
|
||
if (MOZ_UNLIKELY(key.isObject() && IsInsideNursery(&key.toObject()))) {
|
||
rt->gc.storeBuffer.putGeneric(OrderedHashTableRef<ValueSet, UnbarrieredSet>(
|
||
reinterpret_cast<UnbarrieredSet*>(set), key));
|
||
}
|
||
}
|
||
|
||
bool
|
||
MapObject::getKeysAndValuesInterleaved(JSContext* cx, HandleObject obj,
|
||
JS::AutoValueVector* 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;
|
||
|
||
RelocatableValue rval(v);
|
||
if (!map->put(key, rval)) {
|
||
ReportOutOfMemory(cx);
|
||
return false;
|
||
}
|
||
WriteBarrierPost(cx->runtime(), map, key.value());
|
||
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());
|
||
return mapObj;
|
||
}
|
||
|
||
void
|
||
MapObject::finalize(FreeOp* fop, JSObject* obj)
|
||
{
|
||
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()) {
|
||
RootedValue adderVal(cx);
|
||
if (!GetProperty(cx, obj, obj, cx->names().set, &adderVal))
|
||
return false;
|
||
|
||
if (!IsCallable(adderVal))
|
||
return ReportIsNotFunction(cx, adderVal);
|
||
|
||
bool isOriginalAdder = IsNativeFunction(adderVal, MapObject::set);
|
||
RootedValue mapVal(cx, ObjectValue(*obj));
|
||
FastInvokeGuard fig(cx, adderVal);
|
||
InvokeArgs& args2 = fig.args();
|
||
|
||
ForOfIterator iter(cx);
|
||
if (!iter.init(args[0]))
|
||
return false;
|
||
RootedValue pairVal(cx);
|
||
RootedObject pairObj(cx);
|
||
Rooted<HashableValue> hkey(cx);
|
||
ValueMap* map = obj->getData();
|
||
while (true) {
|
||
bool done;
|
||
if (!iter.next(&pairVal, &done))
|
||
return false;
|
||
if (done)
|
||
break;
|
||
if (!pairVal.isObject()) {
|
||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
|
||
JSMSG_INVALID_MAP_ITERABLE, "Map");
|
||
return false;
|
||
}
|
||
|
||
pairObj = &pairVal.toObject();
|
||
if (!pairObj)
|
||
return false;
|
||
|
||
RootedValue key(cx);
|
||
if (!GetElement(cx, pairObj, pairObj, 0, &key))
|
||
return false;
|
||
|
||
RootedValue val(cx);
|
||
if (!GetElement(cx, pairObj, pairObj, 1, &val))
|
||
return false;
|
||
|
||
if (isOriginalAdder) {
|
||
if (!hkey.setValue(cx, key))
|
||
return false;
|
||
|
||
RelocatableValue rval(val);
|
||
if (!map->put(hkey, rval)) {
|
||
ReportOutOfMemory(cx);
|
||
return false;
|
||
}
|
||
WriteBarrierPost(cx->runtime(), map, key);
|
||
} else {
|
||
if (!args2.init(cx, 2))
|
||
return false;
|
||
|
||
args2.setCallee(adderVal);
|
||
args2.setThis(mapVal);
|
||
args2[0].set(key);
|
||
args2[1].set(val);
|
||
|
||
if (!fig.invoke(cx))
|
||
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(CallReceiver call)
|
||
{
|
||
MOZ_ASSERT(call.thisv().isObject());
|
||
MOZ_ASSERT(call.thisv().toObject().hasClass(&MapObject::class_));
|
||
return *call.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);
|
||
RelocatableValue rval(args.get(1));
|
||
if (!map.put(key, rval)) {
|
||
ReportOutOfMemory(cx);
|
||
return false;
|
||
}
|
||
WriteBarrierPost(cx->runtime(), &map, key.value());
|
||
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 RelocatableValue 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 RelocatableValue(), which is the same as
|
||
// RelocatableValue(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 *********************************************************************************/
|
||
|
||
namespace {
|
||
|
||
class SetIteratorObject : public NativeObject
|
||
{
|
||
public:
|
||
static const Class class_;
|
||
|
||
enum { TargetSlot, KindSlot, RangeSlot, SlotCount };
|
||
static const JSFunctionSpec methods[];
|
||
static SetIteratorObject* create(JSContext* cx, HandleObject setobj, ValueSet* data,
|
||
SetObject::IteratorKind kind);
|
||
static bool next(JSContext* cx, unsigned argc, Value* vp);
|
||
static void finalize(FreeOp* fop, JSObject* obj);
|
||
|
||
private:
|
||
static inline bool is(HandleValue v);
|
||
inline ValueSet::Range* range();
|
||
inline SetObject::IteratorKind kind() const;
|
||
static bool next_impl(JSContext* cx, const CallArgs& args);
|
||
};
|
||
|
||
} /* anonymous namespace */
|
||
|
||
const Class SetIteratorObject::class_ = {
|
||
"Set Iterator",
|
||
JSCLASS_HAS_RESERVED_SLOTS(SetIteratorObject::SlotCount),
|
||
nullptr, /* addProperty */
|
||
nullptr, /* delProperty */
|
||
nullptr, /* getProperty */
|
||
nullptr, /* setProperty */
|
||
nullptr, /* enumerate */
|
||
nullptr, /* resolve */
|
||
nullptr, /* mayResolve */
|
||
SetIteratorObject::finalize
|
||
};
|
||
|
||
const JSFunctionSpec SetIteratorObject::methods[] = {
|
||
JS_FN("next", next, 0, 0),
|
||
JS_FS_END
|
||
};
|
||
|
||
inline ValueSet::Range*
|
||
SetIteratorObject::range()
|
||
{
|
||
return static_cast<ValueSet::Range*>(getSlot(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)
|
||
{
|
||
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(KindSlot, Int32Value(int32_t(kind)));
|
||
iterobj->setSlot(RangeSlot, PrivateValue(range));
|
||
return iterobj;
|
||
}
|
||
|
||
void
|
||
SetIteratorObject::finalize(FreeOp* fop, JSObject* obj)
|
||
{
|
||
fop->delete_(obj->as<SetIteratorObject>().range());
|
||
}
|
||
|
||
bool
|
||
SetIteratorObject::is(HandleValue v)
|
||
{
|
||
return v.isObject() && v.toObject().is<SetIteratorObject>();
|
||
}
|
||
|
||
bool
|
||
SetIteratorObject::next_impl(JSContext* cx, const CallArgs& args)
|
||
{
|
||
SetIteratorObject& thisobj = args.thisv().toObject().as<SetIteratorObject>();
|
||
ValueSet::Range* range = thisobj.range();
|
||
RootedValue value(cx);
|
||
bool done;
|
||
|
||
if (!range || range->empty()) {
|
||
js_delete(range);
|
||
thisobj.setReservedSlot(RangeSlot, PrivateValue(nullptr));
|
||
value.setUndefined();
|
||
done = true;
|
||
} else {
|
||
switch (thisobj.kind()) {
|
||
case SetObject::Values:
|
||
value = range->front().get();
|
||
break;
|
||
|
||
case SetObject::Entries: {
|
||
JS::AutoValueArray<2> pair(cx);
|
||
pair[0].set(range->front().get());
|
||
pair[1].set(range->front().get());
|
||
|
||
JSObject* pairObj = NewDenseCopiedArray(cx, 2, pair.begin());
|
||
if (!pairObj)
|
||
return false;
|
||
value.setObject(*pairObj);
|
||
break;
|
||
}
|
||
}
|
||
range->popFront();
|
||
done = false;
|
||
}
|
||
|
||
RootedObject result(cx, CreateItrResultObject(cx, value, done));
|
||
if (!result)
|
||
return false;
|
||
args.rval().setObject(*result);
|
||
|
||
return true;
|
||
}
|
||
|
||
bool
|
||
SetIteratorObject::next(JSContext* cx, unsigned argc, Value* vp)
|
||
{
|
||
CallArgs args = CallArgsFromVp(argc, vp);
|
||
return CallNonGenericMethod(cx, is, next_impl, args);
|
||
}
|
||
|
||
|
||
/*** Set *****************************************************************************************/
|
||
|
||
const Class SetObject::class_ = {
|
||
"Set",
|
||
JSCLASS_HAS_PRIVATE |
|
||
JSCLASS_HAS_CACHED_PROTO(JSProto_Set),
|
||
nullptr, // addProperty
|
||
nullptr, // delProperty
|
||
nullptr, // getProperty
|
||
nullptr, // setProperty
|
||
nullptr, // enumerate
|
||
nullptr, // resolve
|
||
nullptr, // mayResolve
|
||
finalize,
|
||
nullptr, // call
|
||
nullptr, // hasInstance
|
||
nullptr, // construct
|
||
mark
|
||
};
|
||
|
||
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::AutoValueVector* 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 (!set->put(key)) {
|
||
ReportOutOfMemory(cx);
|
||
return false;
|
||
}
|
||
WriteBarrierPost(cx->runtime(), set, key.value());
|
||
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());
|
||
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)
|
||
{
|
||
SetObject* setobj = static_cast<SetObject*>(obj);
|
||
if (ValueSet* set = setobj->getData())
|
||
fop->delete_(set);
|
||
}
|
||
|
||
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 adderVal(cx);
|
||
if (!GetProperty(cx, obj, obj, cx->names().add, &adderVal))
|
||
return false;
|
||
|
||
if (!IsCallable(adderVal))
|
||
return ReportIsNotFunction(cx, adderVal);
|
||
|
||
bool isOriginalAdder = IsNativeFunction(adderVal, SetObject::add);
|
||
RootedValue setVal(cx, ObjectValue(*obj));
|
||
FastInvokeGuard fig(cx, adderVal);
|
||
InvokeArgs& args2 = fig.args();
|
||
|
||
RootedValue keyVal(cx);
|
||
ForOfIterator iter(cx);
|
||
if (!iter.init(args[0]))
|
||
return false;
|
||
Rooted<HashableValue> key(cx);
|
||
ValueSet* set = obj->getData();
|
||
while (true) {
|
||
bool done;
|
||
if (!iter.next(&keyVal, &done))
|
||
return false;
|
||
if (done)
|
||
break;
|
||
|
||
if (isOriginalAdder) {
|
||
if (!key.setValue(cx, keyVal))
|
||
return false;
|
||
if (!set->put(key)) {
|
||
ReportOutOfMemory(cx);
|
||
return false;
|
||
}
|
||
WriteBarrierPost(cx->runtime(), set, keyVal);
|
||
} else {
|
||
if (!args2.init(cx, 1))
|
||
return false;
|
||
|
||
args2.setCallee(adderVal);
|
||
args2.setThis(setVal);
|
||
args2[0].set(keyVal);
|
||
|
||
if (!fig.invoke(cx))
|
||
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(CallReceiver call)
|
||
{
|
||
MOZ_ASSERT(call.thisv().isObject());
|
||
MOZ_ASSERT(call.thisv().toObject().hasClass(&SetObject::class_));
|
||
return *static_cast<SetObject&>(call.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 (!set.put(key)) {
|
||
ReportOutOfMemory(cx);
|
||
return false;
|
||
}
|
||
WriteBarrierPost(cx->runtime(), &set, key.value());
|
||
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);
|
||
}
|
||
|
||
const JSFunctionSpec selfhosting_collection_iterator_methods[] = {
|
||
JS_FN("std_Set_iterator_next", SetIteratorObject::next, 0, 0),
|
||
JS_FS_END
|
||
};
|
||
|
||
bool
|
||
js::InitSelfHostingCollectionIteratorFunctions(JSContext* cx, HandleObject obj)
|
||
{
|
||
return JS_DefineFunctions(cx, obj, selfhosting_collection_iterator_methods);
|
||
}
|
||
|
||
/*** 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;
|
||
InvokeArgs args(cx);
|
||
if (!args.init(cx, 2))
|
||
return false;
|
||
args.setCallee(JS::ObjectValue(*forEachFunc));
|
||
args.setThis(JS::ObjectValue(*obj));
|
||
args[0].set(callbackFn);
|
||
args[1].set(thisArg);
|
||
return Invoke(cx, args);
|
||
}
|
||
|
||
// 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);
|
||
}
|