Bug 805003 - Implement Map and Set clear methods. r=luke.

--HG--
extra : rebase_source : 02c641c57f556f83dbaf6fbf568f0d67e53c9982
This commit is contained in:
Jason Orendorff 2012-10-30 17:02:29 -05:00
parent b880ee6c47
commit 15a89dedfc
26 changed files with 348 additions and 3 deletions

View File

@ -111,6 +111,8 @@ class OrderedHashTable
return false;
}
// clear() requires that members are assigned only after all allocation
// has succeeded, and that this->ranges is left untouched.
hashTable = tableAlloc;
data = dataAlloc;
dataLength = 0;
@ -218,6 +220,42 @@ class OrderedHashTable
return true;
}
/*
* Remove all entries.
*
* Returns false on OOM, leaving the OrderedHashTable and any live Ranges
* in the old state.
*
* The effect on live Ranges is the same as removing all entries; in
* particular, those Ranges are still live and will see any entries added
* after a successful clear().
*/
bool clear() {
if (dataLength != 0) {
Data **oldHashTable = hashTable;
Data *oldData = data;
uint32_t oldDataLength = dataLength;
hashTable = NULL;
if (!init()) {
// init() only mutates members on success; see comment above.
hashTable = oldHashTable;
return false;
}
alloc.free_(oldHashTable);
freeData(oldData, oldDataLength);
for (Range *r = ranges; r; r = r->next)
r->onClear();
}
MOZ_ASSERT(hashTable);
MOZ_ASSERT(data);
MOZ_ASSERT(dataLength == 0);
MOZ_ASSERT(liveCount == 0);
return true;
}
/*
* Ranges are used to iterate over OrderedHashTables.
*
@ -337,6 +375,12 @@ class OrderedHashTable
i = count;
}
/* The hash table calls this when cleared. */
void onClear() {
MOZ_ASSERT(valid());
i = count = 0;
}
bool valid() const {
return next != this;
}
@ -476,9 +520,13 @@ class OrderedHashTable
return 1 << (HashNumberSizeBits - hashShift);
}
void freeData(Data *data, uint32_t length) {
static void destroyData(Data *data, uint32_t length) {
for (Data *p = data + length; p != data; )
(--p)->~Data();
}
void freeData(Data *data, uint32_t length) {
destroyData(data, length);
alloc.free_(data);
}
@ -642,6 +690,7 @@ class OrderedHashMap
Entry *get(const Key &key) { return impl.get(key); }
bool put(const Key &key, const Value &value) { return impl.put(Entry(key, value)); }
bool remove(const Key &key, bool *foundp) { return impl.remove(key, foundp); }
bool clear() { return impl.clear(); }
};
template <class T, class OrderedHashPolicy, class AllocPolicy>
@ -668,6 +717,7 @@ class OrderedHashSet
Range all() { return impl.all(); }
bool put(const T &value) { return impl.put(value); }
bool remove(const T &value, bool *foundp) { return impl.remove(value, foundp); }
bool clear() { return impl.clear(); }
};
} // namespace js
@ -904,6 +954,7 @@ JSFunctionSpec MapObject::methods[] = {
JS_FN("set", set, 2, 0),
JS_FN("delete", delete_, 1, 0),
JS_FN("iterator", iterator, 0, 0),
JS_FN("clear", clear, 0, 0),
JS_FS_END
};
@ -1147,8 +1198,10 @@ MapObject::delete_impl(JSContext *cx, CallArgs args)
ValueMap &map = extract(args);
ARG0_KEY(cx, args, key);
bool found;
if (!map.remove(key, &found))
if (!map.remove(key, &found)) {
js_ReportOutOfMemory(cx);
return false;
}
args.rval().setBoolean(found);
return true;
}
@ -1179,6 +1232,25 @@ MapObject::iterator(JSContext *cx, unsigned argc, Value *vp)
return CallNonGenericMethod(cx, is, iterator_impl, args);
}
bool
MapObject::clear_impl(JSContext *cx, CallArgs args)
{
Rooted<MapObject*> mapobj(cx, &args.thisv().toObject().asMap());
if (!mapobj->getData()->clear()) {
js_ReportOutOfMemory(cx);
return false;
}
args.rval().setUndefined();
return true;
}
JSBool
MapObject::clear(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod(cx, is, clear_impl, args);
}
JSObject *
js_InitMapClass(JSContext *cx, HandleObject obj)
{
@ -1344,6 +1416,7 @@ JSFunctionSpec SetObject::methods[] = {
JS_FN("add", add, 1, 0),
JS_FN("delete", delete_, 1, 0),
JS_FN("iterator", iterator, 0, 0),
JS_FN("clear", clear, 0, 0),
JS_FS_END
};
@ -1488,8 +1561,10 @@ SetObject::delete_impl(JSContext *cx, CallArgs args)
ValueSet &set = extract(args);
ARG0_KEY(cx, args, key);
bool found;
if (!set.remove(key, &found))
if (!set.remove(key, &found)) {
js_ReportOutOfMemory(cx);
return false;
}
args.rval().setBoolean(found);
return true;
}
@ -1520,6 +1595,25 @@ SetObject::iterator(JSContext *cx, unsigned argc, Value *vp)
return CallNonGenericMethod(cx, is, iterator_impl, args);
}
bool
SetObject::clear_impl(JSContext *cx, CallArgs args)
{
Rooted<SetObject*> setobj(cx, &args.thisv().toObject().asSet());
if (!setobj->getData()->clear()) {
js_ReportOutOfMemory(cx);
return false;
}
args.rval().setUndefined();
return true;
}
JSBool
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)
{

View File

@ -105,6 +105,8 @@ class MapObject : public JSObject {
static JSBool delete_(JSContext *cx, unsigned argc, Value *vp);
static bool iterator_impl(JSContext *cx, CallArgs args);
static JSBool iterator(JSContext *cx, unsigned argc, Value *vp);
static bool clear_impl(JSContext *cx, CallArgs args);
static JSBool clear(JSContext *cx, unsigned argc, Value *vp);
};
class SetObject : public JSObject {
@ -131,6 +133,8 @@ class SetObject : public JSObject {
static JSBool delete_(JSContext *cx, unsigned argc, Value *vp);
static bool iterator_impl(JSContext *cx, CallArgs args);
static JSBool iterator(JSContext *cx, unsigned argc, Value *vp);
static bool clear_impl(JSContext *cx, CallArgs args);
static JSBool clear(JSContext *cx, unsigned argc, Value *vp);
};
} /* namespace js */

View File

@ -0,0 +1,8 @@
// Clearing an empty Map has no effect.
var m = Map();
for (var i = 0; i < 2; i++) {
m.clear();
assertEq(m.size(), 0);
assertEq(m.has(undefined), false);
}

View File

@ -0,0 +1,17 @@
// Clearing a Map removes its entries; the Map remains usable afterwards.
var m = Map([["a", "b"], ["b", "c"]]);
assertEq(m.size(), 2);
m.clear();
assertEq(m.size(), 0);
assertEq(m.has("a"), false);
assertEq(m.get("a"), undefined);
assertEq(m.delete("a"), false);
assertEq(m.has("b"), false);
for (var pair of m)
throw "FAIL"; // shouldn't be any pairs
m.set("c", "d");
assertEq(m.size(), 1);
assertEq(m.has("a"), false);
assertEq(m.has("b"), false);

View File

@ -0,0 +1,10 @@
// Clearing a Map with a nontrivial number of elements works.
var m = Map();
for (var i = 0; i < 100; i++)
m.set(i, i);
assertEq(m.size(), i);
m.clear();
assertEq(m.size(), 0);
m.set("a", 1);
assertEq(m.get("a"), 1);

View File

@ -0,0 +1,10 @@
// Clearing a Map after deleting some entries works.
var m = Map([["a", 1], ["b", 2], ["c", 3], ["d", 4]]);
for (var [k, v] of m)
if (k !== "c")
m.delete(k);
m.clear();
assertEq(m.size(), 0);
assertEq(m.has("c"), false);
assertEq(m.has("d"), false);

View File

@ -0,0 +1,14 @@
// Map.clear is unaffected by deleting/monkeypatching Map.prototype.{delete,iterator}.
var data = [["a", 1], ["b", 2]];
var m1 = Map(data), m2 = Map(data);
delete Map.prototype.delete;
delete Map.prototype.iterator;
m1.clear();
assertEq(m1.size(), 0);
Map.prototype.delete = function () { throw "FAIL"; };
Map.prototype.iterator = function () { throw "FAIL"; };
m2.clear();
assertEq(m2.size(), 0);

View File

@ -0,0 +1,6 @@
// Clearing a Map doesn't affect expando properties.
var m = Map();
m.x = 3;
m.clear();
assertEq(m.x, 3);

View File

@ -0,0 +1,12 @@
// Clearing a Map removes any strong references to its keys and values.
load(libdir + "referencesVia.js");
var m = Map();
var k = {}, v = {};
m.set(k, v);
assertEq(referencesVia(m, "key", k), true);
assertEq(referencesVia(m, "value", v), true);
m.clear();
assertEq(referencesVia(m, "key", k), false);
assertEq(referencesVia(m, "value", v), false);

View File

@ -0,0 +1,23 @@
// A Map iterator does not visit entries removed by clear().
load(libdir + "asserts.js");
var m = Map();
var it = m.iterator();
m.clear();
assertThrowsValue(it.next.bind(it), StopIteration);
m = Map([["a", 1], ["b", 2], ["c", 3], ["d", 4]]);
it = m.iterator();
assertEq(it.next()[0], "a");
m.clear();
assertThrowsValue(it.next.bind(it), StopIteration);
var log = "";
m = Map([["a", 1], ["b", 2], ["c", 3], ["d", 4]]);
for (var [k, v] of m) {
log += k + v;
if (k == "b")
m.clear();
}
assertEq(log, "a1b2");

View File

@ -0,0 +1,15 @@
// A Map iterator continues to visit entries added after a clear().
load(libdir + "asserts.js");
var m = Map([["a", 1]]);
var it = m.iterator();
assertEq(it.next()[0], "a");
m.clear();
m.set("b", 2);
var pair = it.next()
assertEq(pair[0], "b");
assertEq(pair[1], 2);
assertThrowsValue(it.next.bind(it), StopIteration);

View File

@ -0,0 +1,10 @@
// A closed Map iterator does not visit new entries added after a clear().
load(libdir + "asserts.js");
var m = Map();
var it = m.iterator();
assertThrowsValue(it.next.bind(it), StopIteration); // close the iterator
m.clear();
m.set("a", 1);
assertThrowsValue(it.next.bind(it), StopIteration);

View File

@ -32,3 +32,4 @@ checkMethod("get", 1);
checkMethod("has", 1);
checkMethod("set", 2);
checkMethod("delete", 1);
checkMethod("clear", 0);

View File

@ -14,6 +14,7 @@ function test(obj) {
testcase(obj, Map.prototype.has, "x");
testcase(obj, Map.prototype.set, "x", 1);
testcase(obj, Map.prototype.delete, "x");
testcase(obj, Map.prototype.clear);
}
test(Map.prototype);

View File

@ -0,0 +1,8 @@
// Clearing an empty Set has no effect.
var s = Set();
for (var i = 0; i < 2; i++) {
s.clear();
assertEq(s.size(), 0);
assertEq(s.has(undefined), false);
}

View File

@ -0,0 +1,16 @@
// Clearing a Set removes its elements; the Set remains usable afterwards.
var s = Set(["x", "y", "z", "z", "y"]);
assertEq(s.size(), 3);
s.clear();
assertEq(s.size(), 0);
assertEq(s.has("x"), false);
assertEq(s.delete("x"), false);
assertEq(s.has("z"), false);
for (var v of s)
throw "FAIL"; // shouldn't be any elements
s.add("y");
assertEq(s.size(), 1);
assertEq(s.has("x"), false);
assertEq(s.has("z"), false);

View File

@ -0,0 +1,10 @@
// Clearing a Set with a nontrivial number of elements works.
var s = Set();
for (var i = 0; i < 100; i++)
s.add(i);
assertEq(s.size(), i);
s.clear();
assertEq(s.size(), 0);
s.add(12);
assertEq(s.has(12), true);

View File

@ -0,0 +1,10 @@
// Clearing a Set after deleting some entries works.
var s = Set(["a", "b", "c", "d"]);
for (var v of s)
if (v !== "c")
s.delete(v);
s.clear();
assertEq(s.size(), 0);
assertEq(s.has("c"), false);
assertEq(s.has("d"), false);

View File

@ -0,0 +1,14 @@
// Set.clear is unaffected by deleting/monkeypatching Set.prototype.{delete,iterator}.
var data = ["a", 1, {}];
var s1 = Set(data), s2 = Set(data);
delete Set.prototype.delete;
delete Set.prototype.iterator;
s1.clear();
assertEq(s1.size(), 0);
Set.prototype.delete = function () { throw "FAIL"; };
Set.prototype.iterator = function () { throw "FAIL"; };
s2.clear();
assertEq(s2.size(), 0);

View File

@ -0,0 +1,6 @@
// Clearing a Set doesn't affect expando properties.
var s = Set();
s.x = 3;
s.clear();
assertEq(s.x, 3);

View File

@ -0,0 +1,10 @@
// Clearing a Set removes any strong references to its elements.
load(libdir + "referencesVia.js");
var s = Set();
var obj = {};
s.add(obj);
assertEq(referencesVia(s, "key", obj), true);
s.clear();
assertEq(referencesVia(s, "key", obj), false);

View File

@ -0,0 +1,23 @@
// A Set iterator does not visit entries removed by clear().
load(libdir + "asserts.js");
var s = Set();
var it = s.iterator();
s.clear();
assertThrowsValue(it.next.bind(it), StopIteration);
s = Set(["a", "b", "c", "d"]);
it = s.iterator();
assertEq(it.next()[0], "a");
s.clear();
assertThrowsValue(it.next.bind(it), StopIteration);
var log = "";
s = Set(["a", "b", "c", "d"]);
for (var v of s) {
log += v;
if (v == "b")
s.clear();
}
assertEq(log, "ab");

View File

@ -0,0 +1,11 @@
// A Set iterator continues to visit entries added after a clear().
load(libdir + "asserts.js");
var s = Set(["a"]);
var it = s.iterator();
assertEq(it.next(), "a");
s.clear();
s.add("b");
assertEq(it.next(), "b");
assertThrowsValue(it.next.bind(it), StopIteration);

View File

@ -0,0 +1,10 @@
// A closed Set iterator does not visit new entries added after a clear().
load(libdir + "asserts.js");
var s = Set();
var it = s.iterator();
assertThrowsValue(it.next.bind(it), StopIteration); // close the iterator
s.clear();
s.add("a");
assertThrowsValue(it.next.bind(it), StopIteration);

View File

@ -30,3 +30,4 @@ function checkMethod(name, arity) {
checkMethod("has", 1);
checkMethod("add", 1);
checkMethod("delete", 1);
checkMethod("clear", 0);

View File

@ -12,6 +12,7 @@ function test(obj) {
testcase(obj, Set.prototype.has, 12);
testcase(obj, Set.prototype.add, 12);
testcase(obj, Set.prototype.delete, 12);
testcase(obj, Set.prototype.clear);
}
test(Set.prototype);