From 59848d7b02d6fa6dd9328f43cdf40d7490f5b403 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Fri, 16 May 2014 09:56:50 +0100 Subject: [PATCH] Bug 982561 - Add tests for weak maps with key delegates r=terrence --- js/src/gc/Zone.h | 12 ++ js/src/jsapi-tests/moz.build | 1 + js/src/jsapi-tests/testWeakMap.cpp | 246 +++++++++++++++++++++++++++++ js/src/jsgc.cpp | 2 + 4 files changed, 261 insertions(+) create mode 100644 js/src/jsapi-tests/testWeakMap.cpp diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index aaa79ca4022..6ab23d77316 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -8,6 +8,7 @@ #define gc_Zone_h #include "mozilla/Atomics.h" +#include "mozilla/DebugOnly.h" #include "mozilla/MemoryReporting.h" #include "jscntxt.h" @@ -147,6 +148,7 @@ struct Zone : public JS::shadow::Zone, bool gcScheduled; GCState gcState; bool gcPreserveCode; + mozilla::DebugOnly gcLastZoneGroupIndex; public: bool isCollecting() const { @@ -228,6 +230,16 @@ struct Zone : public JS::shadow::Zone, return gcState == Finished; } +#ifdef DEBUG + /* + * For testing purposes, return the index of the zone group which this zone + * was swept in in the last GC. + */ + unsigned lastZoneGroupIndex() { + return gcLastZoneGroupIndex; + } +#endif + /* This is updated by both the main and GC helper threads. */ mozilla::Atomic gcBytes; diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build index ab952be20a8..1a8cceca05a 100644 --- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -72,6 +72,7 @@ UNIFIED_SOURCES += [ 'testTypedArrays.cpp', 'testUncaughtError.cpp', 'testUTF8.cpp', + 'testWeakMap.cpp', 'testXDR.cpp', ] diff --git a/js/src/jsapi-tests/testWeakMap.cpp b/js/src/jsapi-tests/testWeakMap.cpp new file mode 100644 index 00000000000..00516a08a67 --- /dev/null +++ b/js/src/jsapi-tests/testWeakMap.cpp @@ -0,0 +1,246 @@ +/* -*- 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 "gc/Zone.h" + +#include "jsapi-tests/tests.h" + +using namespace JS; + +#ifdef JSGC_USE_EXACT_ROOTING + +BEGIN_TEST(testWeakMap_basicOperations) +{ + RootedObject map(cx, NewWeakMapObject(cx)); + CHECK(IsWeakMapObject(map)); + + RootedObject key(cx, newKey()); + CHECK(key); + CHECK(!IsWeakMapObject(key)); + + RootedValue r(cx); + CHECK(GetWeakMapEntry(cx, map, key, &r)); + CHECK(r.isUndefined()); + + CHECK(checkSize(map, 0)); + + RootedValue val(cx, 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_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()); +} + +bool +checkSize(HandleObject map, uint32_t expected) +{ + 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); + + RootedObject map(cx, NewWeakMapObject(cx)); + CHECK(map); + + RootedObject key(cx, newKey()); + CHECK(key); + + RootedObject delegate(cx, newDelegate()); + CHECK(delegate); + + SetKeyDelegate(key, delegate); + + /* + * Perform an incremental GC, introducing an unmarked CCW to force the map + * zone to finish marking before the delegate zone. + */ + CHECK(newCCW(map, delegate)); + GCDebugSlice(rt, true, 1000000); +#ifdef DEBUG + CHECK(map->zone()->lastZoneGroupIndex() < delegate->zone()->lastZoneGroupIndex()); +#endif + + /* Add our entry to the weakmap. */ + RootedValue val(cx, 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, delegate)); + GCDebugSlice(rt, true, 100000); + CHECK(checkSize(map, 1)); + + /* + * Check that the zones finished marking at the same time, which is + * neccessary because of the presence of the delegate and the CCW. + */ +#ifdef DEBUG + CHECK(map->zone()->lastZoneGroupIndex() == delegate->zone()->lastZoneGroupIndex()); +#endif + + /* Check that when the delegate becomes unreacable the entry is removed. */ + delegate = nullptr; + JS_GC(rt); + CHECK(checkSize(map, 0)); + + return true; +} + +static void SetKeyDelegate(JSObject *key, JSObject *delegate) +{ + JS_SetPrivate(key, delegate); +} + +static JSObject *GetKeyDelegate(JSObject *obj) +{ + return static_cast(JS_GetPrivate(obj)); +} + +JSObject *newKey() +{ + static const js::Class keyClass = { + "keyWithDelgate", + JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1), + JS_PropertyStub, /* addProperty */ + JS_DeletePropertyStub, /* delProperty */ + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + JS_NULL_CLASS_SPEC, + { + nullptr, + nullptr, + nullptr, + false, + GetKeyDelegate + }, + JS_NULL_OBJECT_OPS + }; + + RootedObject key(cx); + key = JS_NewObject(cx, + reinterpret_cast(&keyClass), + JS::NullPtr(), + JS::NullPtr()); + if (!key) + return nullptr; + + SetKeyDelegate(key, nullptr); + + return key; +} + +JSObject *newCCW(HandleObject sourceZone, 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. + */ + RootedObject object(cx); + { + JSAutoCompartment ac(cx, destZone); + object = JS_NewObject(cx, nullptr, NullPtr(), NullPtr()); + if (!object) + return nullptr; + } + { + JSAutoCompartment ac(cx, sourceZone); + if (!JS_WrapObject(cx, &object)) + return nullptr; + } + return object; +} + +JSObject *newDelegate() +{ + static const JSClass delegateClass = { + "delegate", + JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_RESERVED_SLOTS(1), + JS_PropertyStub, + JS_DeletePropertyStub, + JS_PropertyStub, + JS_StrictPropertyStub, + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + nullptr, + nullptr, + nullptr, + nullptr, + JS_GlobalObjectTraceHook + }; + + /* Create the global object. */ + JS::CompartmentOptions options; + options.setVersion(JSVERSION_LATEST); + JS::RootedObject global(cx); + global = JS_NewGlobalObject(cx, &delegateClass, nullptr, JS::FireOnNewGlobalHook, options); + JS_SetReservedSlot(global, 0, Int32Value(42)); + + /* + * Ensure the delegate is not in the nursery because for the purpose of this + * test we're going to put it in a private slot where it won't get updated. + */ + JS_GC(rt); + + return global; +} + +bool +checkSize(HandleObject map, uint32_t expected) +{ + 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) + +#endif // JSGC_USE_EXACT_ROOTING diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 4f8da5334fc..2136bf124dc 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3815,6 +3815,8 @@ GCRuntime::beginSweepingZoneGroup() if (rt->sweepZoneCallback) rt->sweepZoneCallback(zone); + + zone->gcLastZoneGroupIndex = zoneGroupIndex; } validateIncrementalMarking();