/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* 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/Barrier.h" #include "js/RootingAPI.h" #include "jsapi-tests/tests.h" struct MyHeap { explicit MyHeap(JSObject* obj) : weak(obj) {} js::WeakRef weak; void trace(JSTracer* trc) { js::TraceWeakEdge(trc, &weak, "weak"); } }; BEGIN_TEST(testGCWeakRef) { CHECK(cx->options().weakRefs()); cx->options().setWeakRefs(false); CHECK(cx->options().weakRefs()); JS::RootedValue v(cx); EXEC("var weakRefTarget = { x: 42 };\n" "var weakRef = new WeakRef(weakRefTarget);\n" "weakRefTarget = null;\n"); // The constructor keeps the target alive until the host clears kept // objects. This must remain true even after callers try the old disabled // option path above. JS_GC(cx); JS_GC(cx); EVAL("weakRef.deref()", &v); CHECK(v.isObject()); JS::ClearWeakRefKeptObjects(cx); v = JS::UndefinedValue(); JS_GC(cx); JS_GC(cx); EVAL("weakRef.deref()", &v); CHECK(v.isUndefined()); EXEC("var weakRefSymbol = Symbol('weak-ref-symbol');\n" "var symbolRef = new WeakRef(weakRefSymbol);\n" "if (symbolRef.deref() !== weakRefSymbol)\n" " throw new Error('WeakRef must accept unique symbols');\n" "var registeredSymbolRejected = false;\n" "try {\n" " new WeakRef(Symbol.for('weak-ref-symbol'));\n" "} catch (e) {\n" " registeredSymbolRejected = e instanceof TypeError;\n" "}\n" "if (!registeredSymbolRejected)\n" " throw new Error('WeakRef must reject registered symbols');\n"); EXEC("var keptTarget = { y: 7 };\n" "var keptRef = new WeakRef(keptTarget);\n"); JS::ClearWeakRefKeptObjects(cx); EXEC("var keptResult = keptRef.deref();\n" "if (keptResult !== keptTarget)\n" " throw new Error('WeakRef deref must return the target');\n" "keptTarget = null;\n" "keptResult = null;\n"); JS_GC(cx); JS_GC(cx); EVAL("keptRef.deref()", &v); CHECK(v.isObject()); JS::ClearWeakRefKeptObjects(cx); v = JS::UndefinedValue(); JS_GC(cx); JS_GC(cx); EVAL("keptRef.deref()", &v); CHECK(v.isUndefined()); // Create an object and add a property to it so that we can read the // property back later to verify that object internals are not garbage. JS::RootedObject obj(cx, JS_NewPlainObject(cx)); CHECK(obj); CHECK(JS_DefineProperty(cx, obj, "x", 42, 0)); // Store the object behind a weak pointer and remove other references. JS::Rooted heap(cx, MyHeap(obj)); obj = nullptr; cx->gc.minorGC(JS::gcreason::API); // The minor collection should have treated the weak ref as a strong ref, // so the object should still be live, despite not having any other live // references. CHECK(heap.get().weak.unbarrieredGet() != nullptr); obj = heap.get().weak; CHECK(JS_GetProperty(cx, obj, "x", &v)); CHECK(v.isInt32()); CHECK(v.toInt32() == 42); // A full collection with a second ref should keep the object as well. CHECK(obj == heap.get().weak); JS_GC(cx); CHECK(obj == heap.get().weak); v = JS::UndefinedValue(); CHECK(JS_GetProperty(cx, obj, "x", &v)); CHECK(v.isInt32()); CHECK(v.toInt32() == 42); // A full collection after nulling the root should collect the object, or // at least null out the weak reference before returning to the mutator. obj = nullptr; JS_GC(cx); CHECK(heap.get().weak == nullptr); return true; } END_TEST(testGCWeakRef)