| /* -*- 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(rt); |
| |
| CHECK(GetWeakMapEntry(cx, map, key, &r)); |
| CHECK(r == val); |
| CHECK(checkSize(map, 1)); |
| |
| key = nullptr; |
| JS_GC(rt); |
| |
| 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(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); |
| JS_GC(rt); |
| 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)); |
| rt->gc.startDebugGC(GC_NORMAL, budget); |
| CHECK(!JS::IsIncrementalGCInProgress(rt)); |
| #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)); |
| rt->gc.startDebugGC(GC_NORMAL, budget); |
| CHECK(!JS::IsIncrementalGCInProgress(rt)); |
| 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(rt); |
| 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::Class keyClass = { |
| "keyWithDelgate", |
| JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1), |
| nullptr, /* addProperty */ |
| nullptr, /* delProperty */ |
| nullptr, /* getProperty */ |
| nullptr, /* setProperty */ |
| nullptr, /* enumerate */ |
| nullptr, /* resolve */ |
| nullptr, /* mayResolve */ |
| nullptr, /* finalize */ |
| nullptr, /* call */ |
| nullptr, /* hasInstance */ |
| nullptr, /* construct */ |
| nullptr, /* trace */ |
| JS_NULL_CLASS_SPEC, |
| { |
| false, |
| GetKeyDelegate |
| }, |
| 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; |
| } |
| return object; |
| } |
| |
| JSObject* newDelegate() |
| { |
| static const js::Class delegateClass = { |
| "delegate", |
| JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_RESERVED_SLOTS(1), |
| nullptr, /* addProperty */ |
| nullptr, /* delProperty */ |
| nullptr, /* getProperty */ |
| nullptr, /* setProperty */ |
| nullptr, /* enumerate */ |
| nullptr, /* resolve */ |
| nullptr, /* mayResolve */ |
| nullptr, /* finalize */ |
| nullptr, /* call */ |
| nullptr, /* hasInstance */ |
| nullptr, /* construct */ |
| JS_GlobalObjectTraceHook, |
| JS_NULL_CLASS_SPEC, |
| { |
| false, |
| nullptr, |
| DelegateObjectMoved |
| }, |
| JS_NULL_OBJECT_OPS |
| }; |
| |
| /* Create the global object. */ |
| JS::CompartmentOptions options; |
| options.setVersion(JSVERSION_LATEST); |
| JS::RootedObject global(cx); |
| global = JS_NewGlobalObject(cx, Jsvalify(&delegateClass), nullptr, JS::FireOnNewGlobalHook, |
| options); |
| 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) |