/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80:
*/ /* 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/. */
// Define a second global in a different zone.
JS::RealmOptions options;
global2.set(JS_NewGlobalObject(cx, globalClasp, nullptr,
JS::FireOnNewGlobalHook, options)); if (!global2) {
fprintf(stderr, "failed to create second global"); returnfalse;
}
// This should always be false, regardless. if (global1->compartment() == global2->compartment()) {
fprintf(stderr, "second global claims to be in global1's compartment"); returnfalse;
}
// This checks that the API obeys the implicit zone request. if (global1->zone() == global2->zone()) {
fprintf(stderr, "global2 is in global1's zone"); returnfalse;
}
// Define an object in compartment 2, that is wrapped by a CCW into // compartment 1.
{
JSAutoRealm ar(cx, global2);
wrappee.set(JS_NewPlainObject(cx)); if (wrappee->compartment() != global2->compartment()) {
fprintf(stderr, "wrappee in wrong compartment"); returnfalse;
}
}
wrapper.set(wrappee); if (!JS_WrapObject(cx, wrapper)) {
fprintf(stderr, "failed to wrap"); returnfalse;
} if (wrappee == wrapper) {
fprintf(stderr, "expected wrapping"); returnfalse;
} if (wrapper->compartment() != global1->compartment()) {
fprintf(stderr, "wrapper in wrong compartment"); returnfalse;
}
returntrue;
}
class CCWTestTracer final : public JS::CallbackTracer { void onChild(JS::GCCellPtr thing, constchar* name) override {
numberOfThingsTraced++;
BEGIN_TEST(testTracingIncomingCCWs) { #ifdef JS_GC_ZEAL // Disable zeal modes because this test needs to control exactly when the GC // happens.
JS::SetGCZeal(cx, 0, 100); #endif
JS_GC(cx);
BEGIN_TEST(testDeadNurseryCCW) { #ifdef JS_GC_ZEAL // Disable zeal modes because this test needs to control exactly when the GC // happens.
JS::SetGCZeal(cx, 0, 100); #endif
JS_GC(cx);
// Now let the obj and wrapper die.
wrappee = wrapper = nullptr;
// Now a GC should clear the CCW.
CHECK(countObjectWrappers(global1->compartment()) == 1);
cx->runtime()->gc.evictNursery();
CHECK(countObjectWrappers(global1->compartment()) == 0);
// Check for corruption of the CCW table by doing a full GC to force sweeping.
JS_GC(cx);
returntrue;
}
END_TEST(testDeadNurseryCCW)
BEGIN_TEST(testLiveNurseryCCW) { #ifdef JS_GC_ZEAL // Disable zeal modes because this test needs to control exactly when the GC // happens.
JS::SetGCZeal(cx, 0, 100); #endif
JS_GC(cx);
// Now a GC should not kill the CCW.
CHECK(countObjectWrappers(global1->compartment()) == 1);
cx->runtime()->gc.evictNursery();
CHECK(countObjectWrappers(global1->compartment()) == 1);
// Check for corruption of the CCW table by doing a full GC to force sweeping.
JS_GC(cx);
returntrue;
}
END_TEST(testLiveNurseryCCW)
BEGIN_TEST(testLiveNurseryWrapperCCW) { #ifdef JS_GC_ZEAL // Disable zeal modes because this test needs to control exactly when the GC // happens.
JS::SetGCZeal(cx, 0, 100); #endif
JS_GC(cx);
// The wrapper contains a strong reference to the wrappee, so just dropping // the reference to the wrappee will not drop the CCW table entry as long // as the wrapper is held strongly. Thus, the minor collection here must // tenure both the wrapper and the wrappee and keep both in the table.
wrappee = nullptr;
// Now a GC should not kill the CCW.
CHECK(countObjectWrappers(global1->compartment()) == 1);
cx->runtime()->gc.evictNursery();
CHECK(countObjectWrappers(global1->compartment()) == 1);
CHECK(!js::gc::IsInsideNursery(wrapper));
// Check for corruption of the CCW table by doing a full GC to force sweeping.
JS_GC(cx);
returntrue;
}
END_TEST(testLiveNurseryWrapperCCW)
BEGIN_TEST(testLiveNurseryWrappeeCCW) { #ifdef JS_GC_ZEAL // Disable zeal modes because this test needs to control exactly when the GC // happens.
JS::SetGCZeal(cx, 0, 100); #endif
JS_GC(cx);
// Let the wrapper die. The wrapper should drop from the table when we GC, // even though there are other non-cross-compartment edges to it.
wrapper = nullptr;
// Now a GC should not kill the CCW.
CHECK(countObjectWrappers(global1->compartment()) == 1);
cx->runtime()->gc.evictNursery();
CHECK(countObjectWrappers(global1->compartment()) == 0);
CHECK(!js::gc::IsInsideNursery(wrappee));
// Check for corruption of the CCW table by doing a full GC to force sweeping.
JS_GC(cx);
#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) { returnfalse;
}
for (size_t i = 0; i < 3000; i++) {
JS::RootedObject subobj(cx, JS_NewObject(cx, nullptr)); if (!subobj) { returnfalse;
} if (!JS_DefineProperty(cx, obj, "obj", subobj, 0)) { returnfalse;
}
leafOwner = obj;
obj = subobj;
leaf = subobj;
}
// Give the leaf owner a second leaf.
{
JS::RootedObject leaf2(cx, JS_NewObject(cx, nullptr)); if (!leaf2) { returnfalse;
} if (!JS_DefineProperty(cx, leafOwner, "leaf2", leaf2, 0)) { returnfalse;
}
}
// This is marked during markRuntime
JS::RootedObjectVector vec(cx); if (!vec.append(root)) { returnfalse;
}
// Tenure everything so intentionally unrooted objects don't move before we // can use them.
AutoGCParameter disableSemispace(cx, JSGC_SEMISPACE_NURSERY_ENABLED, 0);
cx->runtime()->gc.minorGC(JS::GCReason::API);
// Release all roots except for the RootedObjectVector.
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));
AutoGCParameter param(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
rt->gc.startDebugGC(JS::GCOptions::Normal, budget); while (rt->gc.state() != gc::State::Mark) {
rt->gc.debugGCSlice(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(cx));
// And assert that the mark bits are as we expect them to be.
MOZ_ASSERT(vec[0]->asTenured().isMarkedBlack());
MOZ_ASSERT(!leafHandle->asTenured().isMarkedBlack());
MOZ_ASSERT(!leafOwnerHandle->asTenured().isMarkedBlack());
#ifdef DEBUG // Remember the current GC number so we can assert that no GC occurs // between operations. auto currentGCNumber = rt->gc.gcNumber(); #endif
// 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)) { returnfalse;
}
MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber); if (!JS_SetProperty(cx, vec[0], "newobj", leafValueHandle)) { returnfalse;
}
MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber);
MOZ_ASSERT(leafHandle->asTenured().isMarkedBlack());
// 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)) { returnfalse;
}
MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber);
MOZ_ASSERT(!leaf2.toObject().asTenured().isMarkedBlack()); if (!JS_SetProperty(cx, vec[0], "leafcopy", leaf2)) { returnfalse;
}
MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber);
MOZ_ASSERT(!leaf2.toObject().asTenured().isMarkedBlack());
}
// 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)) { returnfalse;
}
returntrue;
}
END_TEST(testIncrementalRoots)
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.