diff options
Diffstat (limited to 'js/src/jsapi-tests/testWeakMap.cpp')
-rw-r--r-- | js/src/jsapi-tests/testWeakMap.cpp | 258 |
1 files changed, 258 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testWeakMap.cpp b/js/src/jsapi-tests/testWeakMap.cpp new file mode 100644 index 000000000..8ee78ad73 --- /dev/null +++ b/js/src/jsapi-tests/testWeakMap.cpp @@ -0,0 +1,258 @@ +/* -*- 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 "jscompartment.h" + +#include "gc/Zone.h" + +#include "jsapi-tests/tests.h" + +JSObject* keyDelegate = nullptr; + +BEGIN_TEST(testWeakMap_basicOperations) +{ + JS::RootedObject map(cx, JS::NewWeakMapObject(cx)); + CHECK(IsWeakMapObject(map)); + + JS::RootedObject key(cx, newKey()); + CHECK(key); + CHECK(!IsWeakMapObject(key)); + + JS::RootedValue r(cx); + CHECK(GetWeakMapEntry(cx, map, key, &r)); + CHECK(r.isUndefined()); + + CHECK(checkSize(map, 0)); + + JS::RootedValue val(cx, JS::Int32Value(1)); + CHECK(SetWeakMapEntry(cx, map, key, val)); + + CHECK(GetWeakMapEntry(cx, map, key, &r)); + CHECK(r == val); + CHECK(checkSize(map, 1)); + + JS_GC(cx); + + CHECK(GetWeakMapEntry(cx, map, key, &r)); + CHECK(r == val); + CHECK(checkSize(map, 1)); + + key = nullptr; + JS_GC(cx); + + CHECK(checkSize(map, 0)); + + return true; +} + +JSObject* newKey() +{ + return JS_NewPlainObject(cx); +} + +bool +checkSize(JS::HandleObject map, uint32_t expected) +{ + JS::RootedObject keys(cx); + CHECK(JS_NondeterministicGetWeakMapKeys(cx, map, &keys)); + + uint32_t length; + CHECK(JS_GetArrayLength(cx, keys, &length)); + CHECK(length == expected); + + return true; +} +END_TEST(testWeakMap_basicOperations) + +BEGIN_TEST(testWeakMap_keyDelegates) +{ + JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_INCREMENTAL); + JS_GC(cx); + JS::RootedObject map(cx, JS::NewWeakMapObject(cx)); + CHECK(map); + + JS::RootedObject key(cx, newKey()); + CHECK(key); + + JS::RootedObject delegate(cx, newDelegate()); + CHECK(delegate); + keyDelegate = delegate; + + JS::RootedObject delegateRoot(cx); + { + JSAutoCompartment ac(cx, delegate); + delegateRoot = JS_NewPlainObject(cx); + CHECK(delegateRoot); + JS::RootedValue delegateValue(cx, JS::ObjectValue(*delegate)); + CHECK(JS_DefineProperty(cx, delegateRoot, "delegate", delegateValue, 0)); + } + delegate = nullptr; + + /* + * Perform an incremental GC, introducing an unmarked CCW to force the map + * zone to finish marking before the delegate zone. + */ + CHECK(newCCW(map, delegateRoot)); + js::SliceBudget budget(js::WorkBudget(1000000)); + cx->gc.startDebugGC(GC_NORMAL, budget); + while (JS::IsIncrementalGCInProgress(cx)) + cx->gc.debugGCSlice(budget); +#ifdef DEBUG + CHECK(map->zone()->lastZoneGroupIndex() < delegateRoot->zone()->lastZoneGroupIndex()); +#endif + + /* Add our entry to the weakmap. */ + JS::RootedValue val(cx, JS::Int32Value(1)); + CHECK(SetWeakMapEntry(cx, map, key, val)); + CHECK(checkSize(map, 1)); + + /* Check the delegate keeps the entry alive even if the key is not reachable. */ + key = nullptr; + CHECK(newCCW(map, delegateRoot)); + budget = js::SliceBudget(js::WorkBudget(100000)); + cx->gc.startDebugGC(GC_NORMAL, budget); + while (JS::IsIncrementalGCInProgress(cx)) + cx->gc.debugGCSlice(budget); + CHECK(checkSize(map, 1)); + + /* + * Check that the zones finished marking at the same time, which is + * necessary because of the presence of the delegate and the CCW. + */ +#ifdef DEBUG + CHECK(map->zone()->lastZoneGroupIndex() == delegateRoot->zone()->lastZoneGroupIndex()); +#endif + + /* Check that when the delegate becomes unreachable the entry is removed. */ + delegateRoot = nullptr; + keyDelegate = nullptr; + JS_GC(cx); + CHECK(checkSize(map, 0)); + + return true; +} + +static void DelegateObjectMoved(JSObject* obj, const JSObject* old) +{ + if (!keyDelegate) + return; // Object got moved before we set keyDelegate to point to it. + + MOZ_RELEASE_ASSERT(keyDelegate == old); + keyDelegate = obj; +} + +static JSObject* GetKeyDelegate(JSObject* obj) +{ + return keyDelegate; +} + +JSObject* newKey() +{ + static const js::ClassExtension keyClassExtension = { + GetKeyDelegate + }; + + static const js::Class keyClass = { + "keyWithDelegate", + JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1), + JS_NULL_CLASS_OPS, + JS_NULL_CLASS_SPEC, + &keyClassExtension, + JS_NULL_OBJECT_OPS + }; + + JS::RootedObject key(cx, JS_NewObject(cx, Jsvalify(&keyClass))); + if (!key) + return nullptr; + + return key; +} + +JSObject* newCCW(JS::HandleObject sourceZone, JS::HandleObject destZone) +{ + /* + * Now ensure that this zone will be swept first by adding a cross + * compartment wrapper to a new objct in the same zone as the + * delegate obejct. + */ + JS::RootedObject object(cx); + { + JSAutoCompartment ac(cx, destZone); + object = JS_NewPlainObject(cx); + if (!object) + return nullptr; + } + { + JSAutoCompartment ac(cx, sourceZone); + if (!JS_WrapObject(cx, &object)) + return nullptr; + } + + // In order to test the SCC algorithm, we need the wrapper/wrappee to be + // tenured. + cx->gc.evictNursery(); + + return object; +} + +JSObject* newDelegate() +{ + static const js::ClassOps delegateClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + nullptr, /* finalize */ + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + JS_GlobalObjectTraceHook, + }; + + static const js::ClassExtension delegateClassExtension = { + nullptr, + DelegateObjectMoved + }; + + static const js::Class delegateClass = { + "delegate", + JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_RESERVED_SLOTS(1), + &delegateClassOps, + JS_NULL_CLASS_SPEC, + &delegateClassExtension, + JS_NULL_OBJECT_OPS + }; + + /* Create the global object. */ + JS::CompartmentOptions options; + options.behaviors().setVersion(JSVERSION_LATEST); + + JS::RootedObject global(cx, JS_NewGlobalObject(cx, Jsvalify(&delegateClass), nullptr, + JS::FireOnNewGlobalHook, options)); + if (!global) + return nullptr; + + JS_SetReservedSlot(global, 0, JS::Int32Value(42)); + return global; +} + +bool +checkSize(JS::HandleObject map, uint32_t expected) +{ + JS::RootedObject keys(cx); + CHECK(JS_NondeterministicGetWeakMapKeys(cx, map, &keys)); + + uint32_t length; + CHECK(JS_GetArrayLength(cx, keys, &length)); + CHECK(length == expected); + + return true; +} +END_TEST(testWeakMap_keyDelegates) |