diff --git a/js/src/jsapi-tests/testGCMarking.cpp b/js/src/jsapi-tests/testGCMarking.cpp index 8ddc2d2e44d1..cfaa87d7c7c3 100644 --- a/js/src/jsapi-tests/testGCMarking.cpp +++ b/js/src/jsapi-tests/testGCMarking.cpp @@ -5,6 +5,10 @@ * 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 "jsapi.h" + +#include "js/RootingAPI.h" +#include "js/SliceBudget.h" #include "jsapi-tests/tests.h" class CCWTestTracer : public JS::CallbackTracer { @@ -74,3 +78,146 @@ BEGIN_TEST(testTracingIncomingCCWs) return true; } END_TEST(testTracingIncomingCCWs) + +BEGIN_TEST(testIncrementalRoots) +{ + JSRuntime* rt = cx->runtime(); + +#ifdef JS_GC_ZEAL + // Disable zeal modes because this test needs to control exactly when the GC happens. + JS_SetGCZeal(cx, 0, 100); +#endif + + // Construct a big object graph to mark. In JS, the resulting object graph + // is equivalent to: + // + // leaf = {}; + // leaf2 = {}; + // root = { 'obj': { 'obj': ... { 'obj': leaf, 'leaf2': leaf2 } ... } } + // + // with leafOwner the object that has the 'obj' and 'leaf2' properties. + + JS::RootedObject obj(cx, JS_NewObject(cx, nullptr)); + if (!obj) + return false; + + JS::RootedObject root(cx, obj); + + JS::RootedObject leaf(cx); + JS::RootedObject leafOwner(cx); + + for (size_t i = 0; i < 3000; i++) { + JS::RootedObject subobj(cx, JS_NewObject(cx, nullptr)); + if (!subobj) + return false; + if (!JS_DefineProperty(cx, obj, "obj", subobj, 0)) + return false; + leafOwner = obj; + obj = subobj; + leaf = subobj; + } + + // Give the leaf owner a second leaf. + { + JS::RootedObject leaf2(cx, JS_NewObject(cx, nullptr)); + if (!leaf2) + return false; + if (!JS_DefineProperty(cx, leafOwner, "leaf2", leaf2, 0)) + return false; + } + + // This is marked during markRuntime + JS::AutoObjectVector vec(cx); + vec.append(root); + + // Tenure everything so intentionally unrooted objects don't move before we + // can use them. + cx->minorGC(JS::gcreason::API); + + // Release all roots except for the AutoObjectVector. + obj = root = nullptr; + + // We need to manipulate interior nodes, but the JSAPI understandably wants + // to make it difficult to do that without rooting things on the stack (by + // requiring Handle parameters). We can do it anyway by using + // fromMarkedLocation. The hazard analysis is OK with this because the + // unrooted variables are not live after they've been pointed to via + // fromMarkedLocation; you're essentially lying to the analysis, saying + // that the unrooted variables are rooted. + // + // The analysis will report this lie in its listing of "unsafe references", + // but we do not break the build based on those as there are too many false + // positives. + JSObject* unrootedLeaf = leaf; + JS::Value unrootedLeafValue = JS::ObjectValue(*leaf); + JSObject* unrootedLeafOwner = leafOwner; + JS::HandleObject leafHandle = JS::HandleObject::fromMarkedLocation(&unrootedLeaf); + JS::HandleValue leafValueHandle = JS::HandleValue::fromMarkedLocation(&unrootedLeafValue); + JS::HandleObject leafOwnerHandle = JS::HandleObject::fromMarkedLocation(&unrootedLeafOwner); + leaf = leafOwner = nullptr; + + // Do the root marking slice. This should mark 'root' and a bunch of its + // descendants. It shouldn't make it all the way through (it gets a budget + // of 1000, and the graph is about 3000 objects deep). + js::SliceBudget budget(js::WorkBudget(1000)); + JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); + rt->gc.startDebugGC(GC_NORMAL, budget); + + // We'd better be between iGC slices now. There's always a risk that + // something will decide that we need to do a full GC (such as gczeal, but + // that is turned off.) + MOZ_ASSERT(JS::IsIncrementalGCInProgress(rt)); + + // And assert that the mark bits are as we expect them to be. + MOZ_ASSERT(vec[0]->asTenured().isMarked()); + MOZ_ASSERT(!leafHandle->asTenured().isMarked()); + MOZ_ASSERT(!leafOwnerHandle->asTenured().isMarked()); + + // Remember the current GC number so we can assert that no GC occurs + // between operations. + auto currentGCNumber = rt->gc.gcNumber(); + + // Now do the incremental GC's worst nightmare: rip an unmarked object + // 'leaf' out of the graph and stick it into an already-marked region (hang + // it off the un-prebarriered root, in fact). The pre-barrier on the + // overwrite of the source location should cause this object to be marked. + if (!JS_SetProperty(cx, leafOwnerHandle, "obj", JS::UndefinedHandleValue)) + return false; + MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber); + if (!JS_SetProperty(cx, vec[0], "newobj", leafValueHandle)) + return false; + MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber); + MOZ_ASSERT(leafHandle->asTenured().isMarked()); + + // Also take an unmarked object 'leaf2' from the graph and add an + // additional edge from the root to it. This will not be marked by any + // pre-barrier, but it is still in the live graph so it will eventually get + // marked. + // + // Note that the root->leaf2 edge will *not* be marked through, since the + // root is already marked, but that only matters if doing a compacting GC + // and the compacting GC repeats the whole marking phase to update + // pointers. + { + JS::RootedValue leaf2(cx); + if (!JS_GetProperty(cx, leafOwnerHandle, "leaf2", &leaf2)) + return false; + MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber); + MOZ_ASSERT(!leaf2.toObject().asTenured().isMarked()); + if (!JS_SetProperty(cx, vec[0], "leafcopy", leaf2)) + return false; + MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber); + MOZ_ASSERT(!leaf2.toObject().asTenured().isMarked()); + } + + // Finish the GC using an unlimited budget. + auto unlimited = js::SliceBudget::unlimited(); + rt->gc.debugGCSlice(unlimited); + + // Access the leaf object to try to trigger a crash if it is dead. + if (!JS_SetProperty(cx, leafHandle, "toes", JS::UndefinedHandleValue)) + return false; + + return true; +} +END_TEST(testIncrementalRoots)