author | Razvan Maries <rmaries@mozilla.com> |
Tue, 21 May 2019 19:29:01 +0300 | |
changeset 474731 | 3c0f78074b727fbae112b6eda111d4c4d30cc3ec |
parent 474730 | 78571bb1f20e643cdab9791f61b0aa7b6a8bdf90 (current diff) |
parent 474684 | 4d7ef530fffba20a2b352cff9fa23438128105a7 (diff) |
child 474732 | 71bf2b9ca6ea2cbd97f4a07242e2ad7f20a1a2e5 |
child 474780 | afefd7bc17fdfd069206b1b127bcca744a9be7c7 |
push id | 113168 |
push user | rmaries@mozilla.com |
push date | Tue, 21 May 2019 16:39:23 +0000 |
treeherder | mozilla-inbound@3c0f78074b72 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 69.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
js/src/gc/GC.cpp | file | annotate | diff | comparison | revisions | |
js/src/gc/Marking.cpp | file | annotate | diff | comparison | revisions | |
js/src/jsapi-tests/testGCWeakRef.cpp | file | annotate | diff | comparison | revisions |
--- a/dom/webidl/MozFrameLoaderOwner.webidl +++ b/dom/webidl/MozFrameLoaderOwner.webidl @@ -11,16 +11,26 @@ dictionary RemotenessOptions { // Used to resume a given channel load within the target process. If present, // it will be used rather than the `src` & `srcdoc` attributes on the // frameloader to control the load behaviour. unsigned long long pendingSwitchID; boolean replaceBrowsingContext = false; }; +/** + * An interface implemented by elements that are 'browsing context containers' + * in HTML5 terms (that is, elements such as iframe that creates a new + * browsing context): + * + * https://html.spec.whatwg.org/#browsing-context-container + * + * Object implementing this interface must implement nsFrameLoaderOwner in + * native C++ code. + */ [NoInterfaceObject] interface MozFrameLoaderOwner { [ChromeOnly] readonly attribute FrameLoader? frameLoader; [ChromeOnly] readonly attribute BrowsingContext? browsingContext;
--- a/js/src/gc/ArenaList.h +++ b/js/src/gc/ArenaList.h @@ -287,17 +287,17 @@ class ArenaLists { // Arena lists which have yet to be swept, but need additional foreground // processing before they are swept. ZoneData<Arena*> gcShapeArenasToUpdate; ZoneData<Arena*> gcAccessorShapeArenasToUpdate; ZoneData<Arena*> gcScriptArenasToUpdate; ZoneData<Arena*> gcObjectGroupArenasToUpdate; - // The list of empty arenas which are collected during sweep phase and + // The list of empty arenas which are collected during the sweep phase and // released at the end of sweeping every sweep group. ZoneData<Arena*> savedEmptyArenas; public: explicit ArenaLists(JS::Zone* zone); ~ArenaLists(); FreeLists& freeLists() { return freeLists_.ref(); } @@ -345,20 +345,16 @@ class ArenaLists { bool foregroundFinalize(FreeOp* fop, AllocKind thingKind, js::SliceBudget& sliceBudget, SortedArenaList& sweepList); static void backgroundFinalize(FreeOp* fop, Arena* listHead, Arena** empty); void setParallelAllocEnabled(bool enabled); - // When finalizing arenas, whether to keep empty arenas on the list or - // release them immediately. - enum KeepArenasEnum { RELEASE_ARENAS, KEEP_ARENAS }; - private: inline JSRuntime* runtime(); inline JSRuntime* runtimeFromAnyThread(); inline void queueForForegroundSweep(FreeOp* fop, const FinalizePhase& phase); inline void queueForBackgroundSweep(FreeOp* fop, const FinalizePhase& phase); inline void queueForForegroundSweep(AllocKind thingKind); inline void queueForBackgroundSweep(AllocKind thingKind);
--- a/js/src/gc/GC.cpp +++ b/js/src/gc/GC.cpp @@ -632,65 +632,56 @@ inline size_t Arena::finalize(FreeOp* fo } // Finalize arenas from src list, releasing empty arenas if keepArenas wasn't // specified and inserting the others into the appropriate destination size // bins. template <typename T> static inline bool FinalizeTypedArenas(FreeOp* fop, Arena** src, SortedArenaList& dest, - AllocKind thingKind, SliceBudget& budget, - ArenaLists::KeepArenasEnum keepArenas) { + AllocKind thingKind, SliceBudget& budget) { // When operating in the foreground, take the lock at the top. Maybe<AutoLockGC> maybeLock; if (fop->onMainThread()) { maybeLock.emplace(fop->runtime()); } - // During background sweeping free arenas are released later on in - // sweepBackgroundThings(). - MOZ_ASSERT_IF(!fop->onMainThread(), keepArenas == ArenaLists::KEEP_ARENAS); - size_t thingSize = Arena::thingSize(thingKind); size_t thingsPerArena = Arena::thingsPerArena(thingKind); while (Arena* arena = *src) { *src = arena->next; size_t nmarked = arena->finalize<T>(fop, thingKind, thingSize); size_t nfree = thingsPerArena - nmarked; if (nmarked) { dest.insertAt(arena, nfree); - } else if (keepArenas == ArenaLists::KEEP_ARENAS) { + } else { arena->chunk()->recycleArena(arena, dest, thingsPerArena); - } else { - fop->runtime()->gc.releaseArena(arena, maybeLock.ref()); } budget.step(thingsPerArena); if (budget.isOverBudget()) { return false; } } return true; } /* * Finalize the list of areans. */ static bool FinalizeArenas(FreeOp* fop, Arena** src, SortedArenaList& dest, - AllocKind thingKind, SliceBudget& budget, - ArenaLists::KeepArenasEnum keepArenas) { + AllocKind thingKind, SliceBudget& budget) { switch (thingKind) { #define EXPAND_CASE(allocKind, traceKind, type, sizedType, bgFinal, nursery, \ compact) \ case AllocKind::allocKind: \ - return FinalizeTypedArenas<type>(fop, src, dest, thingKind, budget, \ - keepArenas); + return FinalizeTypedArenas<type>(fop, src, dest, thingKind, budget); FOR_EACH_ALLOCKIND(EXPAND_CASE) #undef EXPAND_CASE default: MOZ_CRASH("Invalid alloc kind"); } } @@ -3147,18 +3138,17 @@ void ArenaLists::backgroundFinalize(Free AllocKind thingKind = listHead->getAllocKind(); Zone* zone = listHead->zone; size_t thingsPerArena = Arena::thingsPerArena(thingKind); SortedArenaList finalizedSorted(thingsPerArena); auto unlimited = SliceBudget::unlimited(); - FinalizeArenas(fop, &listHead, finalizedSorted, thingKind, unlimited, - KEEP_ARENAS); + FinalizeArenas(fop, &listHead, finalizedSorted, thingKind, unlimited); MOZ_ASSERT(!listHead); finalizedSorted.extractEmpty(empty); // When arenas are queued for background finalization, all arenas are moved // to arenaListsToSweep[], leaving the arenaLists[] empty. However, new // arenas may be allocated before background finalization finishes; now that // finalization is complete, we want to merge these lists back together. @@ -5461,28 +5451,31 @@ static void SweepCompressionTasks(GCPara auto& pending = HelperThreadState().compressionPendingList(lock); for (size_t i = 0; i < pending.length(); i++) { if (pending[i]->shouldCancel()) { HelperThreadState().remove(pending, &i); } } } +void js::gc::SweepLazyScripts(GCParallelTask* task) { + JSRuntime* runtime = task->runtime(); + for (SweepGroupZonesIter zone(runtime); !zone.done(); zone.next()) { + for (auto i = zone->cellIter<LazyScript>(); !i.done(); i.next()) { + WeakHeapPtrScript* edge = &i.unbarrieredGet()->script_; + if (*edge && IsAboutToBeFinalized(edge)) { + *edge = nullptr; + } + } + } +} + static void SweepWeakMaps(GCParallelTask* task) { JSRuntime* runtime = task->runtime(); for (SweepGroupZonesIter zone(runtime); !zone.done(); zone.next()) { - /* Clear all weakrefs that point to unmarked things. */ - for (auto edge : zone->gcWeakRefs()) { - /* Edges may be present multiple times, so may already be nulled. */ - if (*edge && IsAboutToBeFinalizedDuringSweep(**edge)) { - *edge = nullptr; - } - } - zone->gcWeakRefs().clear(); - /* No need to look up any more weakmap keys from this sweep group. */ AutoEnterOOMUnsafeRegion oomUnsafe; if (!zone->gcWeakKeys().clear()) { oomUnsafe.crash("clearing weak keys in beginSweepingSweepGroup()"); } zone->sweepWeakMaps(); } @@ -5730,16 +5723,18 @@ IncrementalProgress GCRuntime::beginSwee AutoRunParallelTask sweepCCWrappers(rt, SweepCCWrappers, PhaseKind::SWEEP_CC_WRAPPER, lock); AutoRunParallelTask sweepObjectGroups(rt, SweepObjectGroups, PhaseKind::SWEEP_TYPE_OBJECT, lock); AutoRunParallelTask sweepMisc(rt, SweepMisc, PhaseKind::SWEEP_MISC, lock); AutoRunParallelTask sweepCompTasks(rt, SweepCompressionTasks, PhaseKind::SWEEP_COMPRESSION, lock); + AutoRunParallelTask sweepLazyScripts(rt, SweepLazyScripts, + PhaseKind::SWEEP_LAZYSCRIPTS, lock); AutoRunParallelTask sweepWeakMaps(rt, SweepWeakMaps, PhaseKind::SWEEP_WEAKMAPS, lock); AutoRunParallelTask sweepUniqueIds(rt, SweepUniqueIds, PhaseKind::SWEEP_UNIQUEIDS, lock); WeakCacheTaskVector sweepCacheTasks; if (!PrepareWeakCacheTasks(rt, &sweepCacheTasks)) { SweepWeakCachesOnMainThread(rt); @@ -5870,34 +5865,31 @@ void GCRuntime::beginSweepPhase(JS::GCRe bool ArenaLists::foregroundFinalize(FreeOp* fop, AllocKind thingKind, SliceBudget& sliceBudget, SortedArenaList& sweepList) { if (!arenaListsToSweep(thingKind) && incrementalSweptArenas.ref().isEmpty()) { return true; } - // Empty object arenas are not released until all foreground GC things have - // been swept. - KeepArenasEnum keepArenas = - IsObjectAllocKind(thingKind) ? KEEP_ARENAS : RELEASE_ARENAS; - + // Empty arenas are not released until all foreground finalized GC things in + // the current sweep group have been finalized. This allows finalizers for + // other cells in the same sweep group to call IsAboutToBeFinalized on cells + // in this arena. if (!FinalizeArenas(fop, &arenaListsToSweep(thingKind), sweepList, thingKind, - sliceBudget, keepArenas)) { + sliceBudget)) { incrementalSweptArenaKind = thingKind; incrementalSweptArenas = sweepList.toArenaList(); return false; } // Clear any previous incremental sweep state we may have saved. incrementalSweptArenas.ref().clear(); - if (IsObjectAllocKind(thingKind)) { - sweepList.extractEmpty(&savedEmptyArenas.ref()); - } + sweepList.extractEmpty(&savedEmptyArenas.ref()); ArenaList finalized = sweepList.toArenaList(); arenaLists(thingKind) = finalized.insertListWithCursorAtEnd(arenaLists(thingKind)); return true; } @@ -5979,23 +5971,24 @@ IncrementalProgress GCRuntime::sweepType gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP_TYPES_END); zone->types.endSweep(rt); } return Finished; } IncrementalProgress GCRuntime::releaseSweptEmptyArenas(FreeOp* fop, - SliceBudget& budget, - Zone* zone) { - // Foreground finalized objects have already been finalized, and now their + SliceBudget& budget) { + // Foreground finalized GC things have already been finalized, and now their // arenas can be reclaimed by freeing empty ones and making non-empty ones // available for allocation. - zone->arenas.releaseForegroundSweptEmptyArenas(); + for (SweepGroupZonesIter zone(rt); !zone.done(); zone.next()) { + zone->arenas.releaseForegroundSweptEmptyArenas(); + } return Finished; } void GCRuntime::startSweepingAtomsTable() { auto& maybeAtoms = maybeAtomsToSweep.ref(); MOZ_ASSERT(maybeAtoms.isNothing()); AtomsTable* atomsTable = rt->atomsForSweeping(); @@ -6554,18 +6547,18 @@ bool GCRuntime::initSweepActions() { Call(&GCRuntime::sweepTypeInformation), MaybeYieldInZoneLoop(ZealMode::YieldBeforeSweepingObjects), ForEachAllocKind(ForegroundObjectFinalizePhase.kinds, Call(&GCRuntime::finalizeAllocKind)), MaybeYieldInZoneLoop(ZealMode::YieldBeforeSweepingNonObjects), ForEachAllocKind(ForegroundNonObjectFinalizePhase.kinds, Call(&GCRuntime::finalizeAllocKind)), MaybeYieldInZoneLoop(ZealMode::YieldBeforeSweepingShapeTrees), - Call(&GCRuntime::sweepShapeTree), - Call(&GCRuntime::releaseSweptEmptyArenas))), + Call(&GCRuntime::sweepShapeTree))), + Call(&GCRuntime::releaseSweptEmptyArenas), Call(&GCRuntime::endSweepingSweepGroup))); return sweepActions != nullptr; } IncrementalProgress GCRuntime::performSweepActions(SliceBudget& budget) { // Marked GC things may vary between recording and replaying, so sweep // actions should not perform any recorded events.
--- a/js/src/gc/GCMarker.h +++ b/js/src/gc/GCMarker.h @@ -250,20 +250,16 @@ class GCMarker : public JSTracer { void traverseObjectEdge(S source, JSObject* target) { traverseEdge(source, target); } template <typename S> void traverseStringEdge(S source, JSString* target) { traverseEdge(source, target); } - // Notes a weak graph edge for later sweeping. - template <typename T> - void noteWeakEdge(T* edge); - /* * Care must be taken changing the mark color from gray to black. The cycle * collector depends on the invariant that there are no black to gray edges * in the GC heap. This invariant lets the CC not trace through black * objects. If this invariant is violated, the cycle collector may free * objects that are still reachable. */ void setMarkColorGray();
--- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -624,18 +624,17 @@ class GCRuntime { void markIncomingCrossCompartmentPointers(MarkColor color); IncrementalProgress beginSweepingSweepGroup(FreeOp* fop, SliceBudget& budget); void sweepDebuggerOnMainThread(FreeOp* fop); void sweepJitDataOnMainThread(FreeOp* fop); IncrementalProgress endSweepingSweepGroup(FreeOp* fop, SliceBudget& budget); IncrementalProgress performSweepActions(SliceBudget& sliceBudget); IncrementalProgress sweepTypeInformation(FreeOp* fop, SliceBudget& budget, Zone* zone); - IncrementalProgress releaseSweptEmptyArenas(FreeOp* fop, SliceBudget& budget, - Zone* zone); + IncrementalProgress releaseSweptEmptyArenas(FreeOp* fop, SliceBudget& budget); void startSweepingAtomsTable(); IncrementalProgress sweepAtomsTable(FreeOp* fop, SliceBudget& budget); IncrementalProgress sweepWeakCaches(FreeOp* fop, SliceBudget& budget); IncrementalProgress finalizeAllocKind(FreeOp* fop, SliceBudget& budget, Zone* zone, AllocKind kind); IncrementalProgress sweepShapeTree(FreeOp* fop, SliceBudget& budget, Zone* zone); void endSweepPhase(bool lastGC);
--- a/js/src/gc/GenerateStatsPhases.py +++ b/js/src/gc/GenerateStatsPhases.py @@ -123,16 +123,17 @@ PhaseKindGraphRoots = [ PhaseKind("SWEEP_INNER_VIEWS", "Sweep Inner Views", 22), PhaseKind("SWEEP_CC_WRAPPER", "Sweep Cross Compartment Wrappers", 23), PhaseKind("SWEEP_BASE_SHAPE", "Sweep Base Shapes", 24), PhaseKind("SWEEP_INITIAL_SHAPE", "Sweep Initial Shapes", 25), PhaseKind("SWEEP_TYPE_OBJECT", "Sweep Type Objects", 26), PhaseKind("SWEEP_BREAKPOINT", "Sweep Breakpoints", 27), PhaseKind("SWEEP_REGEXP", "Sweep Regexps", 28), PhaseKind("SWEEP_COMPRESSION", "Sweep Compression Tasks", 62), + PhaseKind("SWEEP_LAZYSCRIPTS", "Sweep LazyScripts", 71), PhaseKind("SWEEP_WEAKMAPS", "Sweep WeakMaps", 63), PhaseKind("SWEEP_UNIQUEIDS", "Sweep Unique IDs", 64), PhaseKind("SWEEP_JIT_DATA", "Sweep JIT Data", 65), PhaseKind("SWEEP_WEAK_CACHES", "Sweep Weak Caches", 66), PhaseKind("SWEEP_MISC", "Sweep Miscellaneous", 29), PhaseKind("SWEEP_TYPES", "Sweep type information", 30, [ PhaseKind("SWEEP_TYPES_BEGIN", "Sweep type tables and compilations", 31), PhaseKind("SWEEP_TYPES_END", "Free type arena", 32),
--- a/js/src/gc/Heap-inl.h +++ b/js/src/gc/Heap-inl.h @@ -10,17 +10,17 @@ #include "gc/Heap.h" #include "gc/StoreBuffer.h" #include "gc/Zone.h" inline void js::gc::Arena::init(JS::Zone* zoneArg, AllocKind kind, const AutoLockGC& lock) { MOZ_ASSERT(firstFreeSpan.isEmpty()); - MOZ_ASSERT(!zone); + MOZ_ASSERT((uintptr_t(zone) & 0xff) == JS_FREED_ARENA_PATTERN); MOZ_ASSERT(!allocated()); MOZ_ASSERT(!onDelayedMarkingList_); MOZ_ASSERT(!hasDelayedBlackMarking_); MOZ_ASSERT(!hasDelayedGrayMarking_); MOZ_ASSERT(!nextDelayedMarkingArena_); MOZ_MAKE_MEM_UNDEFINED(this, ArenaSize);
--- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -270,17 +270,21 @@ class Arena { FreeSpan* last = firstFreeSpan.nextSpanUnchecked(this); last->initAsEmpty(); } // Initialize an arena to its unallocated state. For arenas that were // previously allocated for some zone, use release() instead. void setAsNotAllocated() { firstFreeSpan.initAsEmpty(); - zone = nullptr; + + // Poison zone pointer to highlight UAF on released arenas in crash data. + AlwaysPoison(&zone, JS_FREED_ARENA_PATTERN, sizeof(zone), + MemCheckKind::MakeUndefined); + allocKind = size_t(AllocKind::LIMIT); onDelayedMarkingList_ = 0; hasDelayedBlackMarking_ = 0; hasDelayedGrayMarking_ = 0; nextDelayedMarkingArena_ = 0; bufferedCells_ = nullptr; }
--- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -399,20 +399,16 @@ void js::gc::AssertRootMarkingPhase(JSTr template <typename T> T* DoCallback(JS::CallbackTracer* trc, T** thingp, const char* name); template <typename T> T DoCallback(JS::CallbackTracer* trc, T* thingp, const char* name); template <typename T> void DoMarking(GCMarker* gcmarker, T* thing); template <typename T> void DoMarking(GCMarker* gcmarker, const T& thing); -template <typename T> -void NoteWeakEdge(GCMarker* gcmarker, T** thingp); -template <typename T> -void NoteWeakEdge(GCMarker* gcmarker, T* thingp); template <typename T> JS_PUBLIC_API void js::gc::TraceExternalEdge(JSTracer* trc, T* thingp, const char* name) { MOZ_ASSERT(InternalBarrierMethods<T>::isMarkable(*thingp)); TraceEdgeInternal(trc, ConvertToBase(thingp), name); } @@ -442,17 +438,16 @@ FOR_EACH_PUBLIC_GC_POINTER_TYPE(INSTANTI FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(INSTANTIATE_PUBLIC_TRACE_FUNCTIONS) #undef INSTANTIATE_PUBLIC_TRACE_FUNCTIONS namespace js { namespace gc { #define INSTANTIATE_INTERNAL_TRACE_FUNCTIONS(type) \ template void TraceEdgeInternal<type>(JSTracer*, type*, const char*); \ - template void TraceWeakEdgeInternal<type>(JSTracer*, type*, const char*); \ template void TraceRangeInternal<type>(JSTracer*, size_t len, type*, \ const char*); #define INSTANTIATE_INTERNAL_TRACE_FUNCTIONS_FROM_TRACEKIND(_1, type, _2, _3) \ INSTANTIATE_INTERNAL_TRACE_FUNCTIONS(type*) JS_FOR_EACH_TRACEKIND(INSTANTIATE_INTERNAL_TRACE_FUNCTIONS_FROM_TRACEKIND) FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(INSTANTIATE_INTERNAL_TRACE_FUNCTIONS) @@ -564,29 +559,16 @@ void js::gc::TraceEdgeInternal(JSTracer* if (trc->isTenuringTracer()) { return static_cast<TenuringTracer*>(trc)->traverse(thingp); } MOZ_ASSERT(trc->isCallbackTracer()); DoCallback(trc->asCallbackTracer(), thingp, name); } template <typename T> -void js::gc::TraceWeakEdgeInternal(JSTracer* trc, T* thingp, const char* name) { - if (!trc->isMarkingTracer()) { - // Non-marking tracers can select whether or not they see weak edges. - if (trc->traceWeakEdges()) { - TraceEdgeInternal(trc, thingp, name); - } - return; - } - - NoteWeakEdge(GCMarker::fromTracer(trc), thingp); -} - -template <typename T> void js::gc::TraceRangeInternal(JSTracer* trc, size_t len, T* vec, const char* name) { JS::AutoTracingIndex index(trc); for (auto i : IntegerRange(len)) { if (InternalBarrierMethods<T>::isMarkable(vec[i])) { TraceEdgeInternal(trc, &vec[i], name); } ++index; @@ -757,54 +739,16 @@ JS_PUBLIC_API void js::gc::PerformIncrem // Mark the argument, as DoMarking above. ApplyGCThingTyped(thing, [gcmarker](auto thing) { MOZ_ASSERT(ShouldMark(gcmarker, thing)); CheckTracedThing(gcmarker, thing); gcmarker->traverse(thing); }); } -template <typename T> -void NoteWeakEdge(GCMarker* gcmarker, T** thingp) { - // Do per-type marking precondition checks. - if (!ShouldMark(gcmarker, *thingp)) { - return; - } - - CheckTracedThing(gcmarker, *thingp); - - // If the target is already marked, there's no need to store the edge. - if (IsMarkedUnbarriered(gcmarker->runtime(), thingp)) { - return; - } - - gcmarker->noteWeakEdge(thingp); -} - -template <typename T> -void NoteWeakEdge(GCMarker* gcmarker, T* thingp) { - MOZ_CRASH("the gc does not support tagged pointers as weak edges"); -} - -template <typename T> -void js::GCMarker::noteWeakEdge(T* edge) { - static_assert(IsBaseOf<Cell, typename mozilla::RemovePointer<T>::Type>::value, - "edge must point to a GC pointer"); - MOZ_ASSERT((*edge)->isTenured()); - - // Note: we really want the *source* Zone here. The edge may start in a - // non-gc heap location, however, so we use the fact that cross-zone weak - // references are not allowed and use the *target's* zone. - JS::Zone::WeakEdges& weakRefs = (*edge)->asTenured().zone()->gcWeakRefs(); - AutoEnterOOMUnsafeRegion oomUnsafe; - if (!weakRefs.append(reinterpret_cast<TenuredCell**>(edge))) { - oomUnsafe.crash("Failed to record a weak edge for sweeping."); - } -} - // The simplest traversal calls out to the fully generic traceChildren function // to visit the child edges. In the absence of other traversal mechanisms, this // function will rapidly grow the stack past its bounds and crash the process. // Thus, this generic tracing should only be used in cases where subsequent // tracing will not recurse. template <typename T> void js::GCMarker::markAndTraceChildren(T* thing) { if (ThingIsPermanentAtomOrWellKnownSymbol(thing)) { @@ -986,18 +930,18 @@ bool js::GCMarker::mark(T* thing) { /*** Inline, Eager GC Marking ***********************************************/ // Each of the eager, inline marking paths is directly preceeded by the // out-of-line, generic tracing code for comparison. Both paths must end up // traversing equivalent subgraphs. void LazyScript::traceChildren(JSTracer* trc) { - if (script_) { - TraceWeakEdge(trc, &script_, "script"); + if (trc->traceWeakEdges()) { + TraceNullableEdge(trc, &script_, "script"); } if (function_) { TraceEdge(trc, &function_, "function"); } if (sourceObject_) { TraceEdge(trc, &sourceObject_, "sourceObject"); @@ -1024,19 +968,17 @@ void LazyScript::traceChildren(JSTracer* } } if (trc->isMarkingTracer()) { GCMarker::fromTracer(trc)->markImplicitEdges(this); } } inline void js::GCMarker::eagerlyMarkChildren(LazyScript* thing) { - if (thing->script_) { - noteWeakEdge(thing->script_.unsafeUnbarrieredForTracing()); - } + // script_ is weak so is not traced here. if (thing->function_) { traverseEdge(thing, static_cast<JSObject*>(thing->function_)); } if (thing->sourceObject_) { traverseEdge(thing, static_cast<JSObject*>(thing->sourceObject_)); } @@ -1543,16 +1485,17 @@ bool GCMarker::markUntilBudgetExhausted( if (budget.isOverBudget()) { return false; } } if (hasGrayEntries()) { AutoSetMarkColor autoSetGray(*this, MarkColor::Gray); do { + MOZ_ASSERT(!hasBlackEntries()); processMarkStackTop(budget); if (budget.isOverBudget()) { return false; } } while (hasGrayEntries()); } if (!hasDelayedChildren()) {
--- a/js/src/gc/Tracer.h +++ b/js/src/gc/Tracer.h @@ -89,31 +89,39 @@ template <typename T> typename PtrBaseGCType<T>::type* ConvertToBase(T* thingp) { return reinterpret_cast<typename PtrBaseGCType<T>::type*>(thingp); } // Internal methods to trace edges. template <typename T> void TraceEdgeInternal(JSTracer* trc, T* thingp, const char* name); template <typename T> -void TraceWeakEdgeInternal(JSTracer* trc, T* thingp, const char* name); -template <typename T> void TraceRangeInternal(JSTracer* trc, size_t len, T* vec, const char* name); #ifdef DEBUG void AssertRootMarkingPhase(JSTracer* trc); #else inline void AssertRootMarkingPhase(JSTracer* trc) {} #endif } // namespace gc -// Trace through an edge in the live object graph on behalf of tracing. The -// effect of tracing the edge depends on the JSTracer being used. For pointer -// types, |*thingp| must not be null. +// Trace through a strong edge in the live object graph on behalf of +// tracing. The effect of tracing the edge depends on the JSTracer being +// used. For pointer types, |*thingp| must not be null. +// +// Note that weak edges are handled separately. GC things with weak edges must +// not trace those edges during marking tracing (which would keep the referent +// alive) but instead arrange for the edge to be swept by calling +// js::gc::IsAboutToBeFinalized during sweeping. For example, see the treatment +// of the script_ edge in LazyScript::traceChildren and +// js::gc::SweepLazyScripts. +// +// GC things that are weakly held in containers can use WeakMap or a container +// wrapped in the WeakCache<> template to perform the appropriate sweeping. template <typename T> inline void TraceEdge(JSTracer* trc, WriteBarriered<T>* thingp, const char* name) { gc::TraceEdgeInternal( trc, gc::ConvertToBase(thingp->unsafeUnbarrieredForTracing()), name); } @@ -180,26 +188,16 @@ inline void TraceNullableRoot(JSTracer* // separate from TraceEdge to make accidental use of such edges more obvious. template <typename T> inline void TraceManuallyBarrieredEdge(JSTracer* trc, T* thingp, const char* name) { gc::TraceEdgeInternal(trc, gc::ConvertToBase(thingp), name); } -// Visits a WeakRef, but does not trace its referents. If *thingp is not marked -// at the end of marking, it is replaced by nullptr. This method records -// thingp, so the edge location must not change after this function is called. - -template <typename T> -inline void TraceWeakEdge(JSTracer* trc, WeakHeapPtr<T>* thingp, const char* name) { - gc::TraceWeakEdgeInternal( - trc, gc::ConvertToBase(thingp->unsafeUnbarrieredForTracing()), name); -} - // Trace all edges contained in the given array. template <typename T> void TraceRange(JSTracer* trc, size_t len, WriteBarriered<T>* vec, const char* name) { gc::TraceRangeInternal( trc, len, gc::ConvertToBase(vec[0].unsafeUnbarrieredForTracing()), name); }
--- a/js/src/gc/Verifier.cpp +++ b/js/src/gc/Verifier.cpp @@ -1,14 +1,16 @@ /* -*- 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/. */ +#include "gc/Verifier.h" + #include "mozilla/DebugOnly.h" #include "mozilla/IntegerPrintfMacros.h" #include "mozilla/Sprintf.h" #ifdef MOZ_VALGRIND # include <valgrind/memcheck.h> #endif @@ -442,33 +444,16 @@ void js::gc::GCRuntime::finishVerifier() verifyPreData = nullptr; } } #endif /* JS_GC_ZEAL */ #if defined(JS_GC_ZEAL) || defined(DEBUG) -// Like gc::MarkColor but allows the possibility of the cell being -// unmarked. Order is important here, with white being 'least marked' -// and black being 'most marked'. -enum class CellColor : uint8_t { White = 0, Gray = 1, Black = 2 }; - -static CellColor GetCellColor(Cell* cell) { - if (cell->isMarkedBlack()) { - return CellColor::Black; - } - - if (cell->isMarkedGray()) { - return CellColor::Gray; - } - - return CellColor::White; -} - static const char* CellColorName(CellColor color) { switch (color) { case CellColor::White: return "white"; case CellColor::Black: return "black"; case CellColor::Gray: return "gray"; @@ -793,17 +778,17 @@ bool js::gc::CheckWeakMapEntryMarking(co // Values belonging to other runtimes or in uncollected zones are treated as // black. CellColor valueColor = CellColor::Black; if (value->runtimeFromAnyThread() == zone->runtimeFromAnyThread() && valueZone->isGCMarking()) { valueColor = GetCellColor(value); } - if (valueColor < Min(mapColor, keyColor)) { + if (valueColor < ExpectedWeakMapValueColor(mapColor, keyColor)) { fprintf(stderr, "WeakMap value is less marked than map and key\n"); fprintf(stderr, "(map %p is %s, key %p is %s, value %p is %s)\n", map, CellColorName(mapColor), key, CellColorName(keyColor), value, CellColorName(valueColor)); ok = false; } // Debugger weak maps map have keys in zones that are not or are @@ -822,17 +807,17 @@ bool js::gc::CheckWeakMapEntryMarking(co CellColor delegateColor; if (delegate->zone()->isGCMarking() || delegate->zone()->isGCSweeping()) { delegateColor = GetCellColor(delegate); } else { // IsMarked() assumes cells in uncollected zones are marked. delegateColor = CellColor::Black; } - if (keyColor < Min(mapColor, delegateColor)) { + if (keyColor < ExpectedWeakMapKeyColor(mapColor, delegateColor)) { fprintf(stderr, "WeakMap key is less marked than map and delegate\n"); fprintf(stderr, "(map %p is %s, delegate %p is %s, key %p is %s)\n", map, CellColorName(mapColor), delegate, CellColorName(delegateColor), key, CellColorName(keyColor)); ok = false; } return ok;
new file mode 100644 --- /dev/null +++ b/js/src/gc/Verifier.h @@ -0,0 +1,62 @@ +/* -*- 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/. */ + +/* + * Internal header for definitions shared between the verifier and jsapi tests. + */ + +#ifndef gc_Verifier_h +#define gc_Verifier_h + +#include "gc/Cell.h" + +namespace js { +namespace gc { + +// Like gc::MarkColor but allows the possibility of the cell being +// unmarked. Order is important here, with white being 'least marked' +// and black being 'most marked'. +enum class CellColor : uint8_t { White = 0, Gray = 1, Black = 2 }; + +static constexpr CellColor AllCellColors[] = { + CellColor::White, CellColor::Gray, CellColor::Black +}; + +static constexpr CellColor MarkedCellColors[] = { + CellColor::Gray, CellColor::Black +}; + +inline CellColor GetCellColor(Cell* cell) { + if (cell->isMarkedBlack()) { + return CellColor::Black; + } + + if (cell->isMarkedGray()) { + return CellColor::Gray; + } + + return CellColor::White; +} + +inline CellColor ExpectedWeakMapValueColor(CellColor keyColor, + CellColor mapColor) { + return Min(keyColor, mapColor); +} + +inline CellColor ExpectedWeakMapKeyColor(CellColor mapColor, + CellColor delegateColor) { + return Min(mapColor, delegateColor); +} + +inline CellColor ExpectedKeyAndDelegateColor(CellColor keyColor, + CellColor delegateColor) { + return Max(keyColor, delegateColor); +} + +} // namespace gc +} // namespace js + +#endif /* gc_Verifier_h */
--- a/js/src/gc/WeakMap-inl.h +++ b/js/src/gc/WeakMap-inl.h @@ -91,16 +91,25 @@ template <class K, class V> void WeakMap<K, V>::trace(JSTracer* trc) { MOZ_ASSERT_IF(JS::RuntimeHeapIsBusy(), isInList()); TraceNullableEdge(trc, &memberOf, "WeakMap owner"); if (trc->isMarkingTracer()) { MOZ_ASSERT(trc->weakMapAction() == ExpandWeakMaps); auto marker = GCMarker::fromTracer(trc); + + // Don't change the map color from black to gray. This can happen when a + // barrier pushes the map object onto the black mark stack when it's already + // present on the gray mark stack, which is marked later. + if (marked && markColor == gc::MarkColor::Black && + marker->markColor() == gc::MarkColor::Gray) { + return; + } + marked = true; markColor = marker->markColor(); (void)markIteratively(marker); return; } if (trc->weakMapAction() == DoNotTraceWeakMaps) { return;
--- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -37,17 +37,16 @@ JS::Zone::Zone(JSRuntime* rt) uniqueIds_(this), suppressAllocationMetadataBuilder(this, false), arenas(this), tenuredAllocsSinceMinorGC_(0), types(this), gcWeakMapList_(this), compartments_(), gcGrayRoots_(this), - gcWeakRefs_(this), weakCaches_(this), gcWeakKeys_(this, SystemAllocPolicy(), rt->randomHashCodeScrambler()), typeDescrObjects_(this, this), markedAtoms_(this), atomCache_(this), externalStringCache_(this), functionToStringCache_(this), keepAtomsCount(this, 0),
--- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -384,26 +384,16 @@ class Zone : public JS::shadow::Zone, js::SystemAllocPolicy>; private: js::ZoneOrGCTaskData<GrayRootVector> gcGrayRoots_; public: GrayRootVector& gcGrayRoots() { return gcGrayRoots_.ref(); } - // This zone's weak edges found via graph traversal during marking, - // preserved for re-scanning during sweeping. - using WeakEdges = js::Vector<js::gc::TenuredCell**, 0, js::SystemAllocPolicy>; - - private: - js::ZoneOrGCTaskData<WeakEdges> gcWeakRefs_; - - public: - WeakEdges& gcWeakRefs() { return gcWeakRefs_.ref(); } - private: // List of non-ephemeron weak containers to sweep during // beginSweepingSweepGroup. js::ZoneOrGCTaskData<mozilla::LinkedList<detail::WeakCacheBase>> weakCaches_; public: mozilla::LinkedList<detail::WeakCacheBase>& weakCaches() { return weakCaches_.ref();
--- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -53,17 +53,16 @@ UNIFIED_SOURCES += [ 'testGCGrayMarking.cpp', 'testGCHeapPostBarriers.cpp', 'testGCHooks.cpp', 'testGCMarking.cpp', 'testGCOutOfMemory.cpp', 'testGCStoreBufferRemoval.cpp', 'testGCUniqueId.cpp', 'testGCWeakCache.cpp', - 'testGCWeakRef.cpp', 'testGetPropertyDescriptor.cpp', 'testHashTable.cpp', 'testIndexToString.cpp', 'testInformalValueTypeName.cpp', 'testIntern.cpp', 'testIntlAvailableLocales.cpp', 'testIntString.cpp', 'testIsInsideNursery.cpp',
--- a/js/src/jsapi-tests/testGCGrayMarking.cpp +++ b/js/src/jsapi-tests/testGCGrayMarking.cpp @@ -1,16 +1,17 @@ /* -*- 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/. */ #include "gc/Heap.h" +#include "gc/Verifier.h" #include "gc/WeakMap.h" #include "gc/Zone.h" #include "js/Proxy.h" #include "jsapi-tests/tests.h" using namespace js; using namespace js::gc; @@ -51,19 +52,24 @@ BEGIN_TEST(testGCGrayMarking) { AutoLeaveZeal nozeal(cx); #endif /* JS_GC_ZEAL */ CHECK(InitGlobals()); JSAutoRealm ar(cx, global1); InitGrayRootTracer(); - bool ok = TestMarking() && TestWeakMaps() && TestUnassociatedWeakMaps() && + // Enable incremental GC. + JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_ZONE_INCREMENTAL); + + bool ok = TestMarking() && TestJSWeakMaps() && TestInternalWeakMaps() && TestCCWs() && TestGrayUnmarking(); + JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_GLOBAL); + global1 = nullptr; global2 = nullptr; RemoveGrayRootTracer(); return ok; } bool TestMarking() { @@ -144,288 +150,324 @@ bool TestMarking() { blackRoot1 = nullptr; blackRoot2 = nullptr; grayRoots.grayRoot1 = nullptr; grayRoots.grayRoot2 = nullptr; return true; } -bool TestWeakMaps() { - JSObject* weakMap = JS::NewWeakMapObject(cx); - CHECK(weakMap); +static const CellColor DontMark = CellColor::White; + +enum MarkKeyOrDelegate : bool { MarkKey = true, MarkDelegate = false }; - JSObject* key; - JSObject* value; - { - JS::RootedObject rootedMap(cx, weakMap); - - key = AllocWeakmapKeyObject(); - CHECK(key); - - value = AllocPlainObject(); - CHECK(value); - - weakMap = rootedMap; +bool TestJSWeakMaps() { + for (auto keyOrDelegateColor : MarkedCellColors) { + for (auto mapColor : MarkedCellColors) { + for (auto markKeyOrDelegate : { MarkKey, MarkDelegate }) { + CellColor expected = ExpectedWeakMapValueColor(keyOrDelegateColor, + mapColor); + CHECK(TestJSWeakMap(markKeyOrDelegate, keyOrDelegateColor, mapColor, + expected)); +#ifdef JS_GC_ZEAL + CHECK(TestJSWeakMapWithGrayUnmarking(markKeyOrDelegate, + keyOrDelegateColor, mapColor, + expected)); +#endif + } + } } - { - JS::RootedObject rootedMap(cx, weakMap); - JS::RootedObject rootedKey(cx, key); - JS::RootedValue rootedValue(cx, ObjectValue(*value)); - CHECK(SetWeakMapEntry(cx, rootedMap, rootedKey, rootedValue)); + return true; +} + +bool TestInternalWeakMaps() { + for (auto keyMarkColor : AllCellColors) { + for (auto delegateMarkColor : AllCellColors) { + if (keyMarkColor == CellColor::White && + delegateMarkColor == CellColor::White) { + continue; + } + + CellColor keyOrDelegateColor = + ExpectedKeyAndDelegateColor(keyMarkColor, delegateMarkColor); + CellColor expected = ExpectedWeakMapValueColor(keyOrDelegateColor, + CellColor::Black); + CHECK(TestInternalWeakMap(keyMarkColor, delegateMarkColor, expected)); + +#ifdef JS_GC_ZEAL + CHECK(TestInternalWeakMapWithGrayUnmarking(keyMarkColor, + delegateMarkColor, + expected)); +#endif + } } - // Test the value of a weakmap entry is marked gray by GC if both the - // weakmap and key are marked gray. + return true; +} - grayRoots.grayRoot1 = weakMap; - grayRoots.grayRoot2 = key; - JS_GC(cx); - CHECK(IsMarkedGray(weakMap)); - CHECK(IsMarkedGray(key)); - CHECK(IsMarkedGray(value)); - - // Test the value of a weakmap entry is marked gray by GC if one of the - // weakmap and the key is marked gray and the other black. +bool TestJSWeakMap(MarkKeyOrDelegate markKey, CellColor weakMapMarkColor, + CellColor keyOrDelegateMarkColor, + CellColor expectedValueColor) { + // Test marking a JS WeakMap object. + // + // This marks the map and one of the key or delegate. The key/delegate and the + // value can end up different colors depending on the color of the map. - JS::RootedObject blackRoot1(cx); - blackRoot1 = weakMap; - grayRoots.grayRoot1 = key; - grayRoots.grayRoot2 = nullptr; - JS_GC(cx); - CHECK(IsMarkedBlack(weakMap)); - CHECK(IsMarkedGray(key)); - CHECK(IsMarkedGray(value)); + JSObject* weakMap; + JSObject* key; + JSObject* value; - blackRoot1 = key; - grayRoots.grayRoot1 = weakMap; - grayRoots.grayRoot2 = nullptr; - JS_GC(cx); - CHECK(IsMarkedGray(weakMap)); - CHECK(IsMarkedBlack(key)); - CHECK(IsMarkedGray(value)); + // If both map and key are marked the same color, test both possible + // orderings. + unsigned markOrderings = weakMapMarkColor == keyOrDelegateMarkColor ? 2 : 1; + + for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) { + CHECK(CreateJSWeakMapObjects(&weakMap, &key, &value)); + + JSObject* delegate = UncheckedUnwrapWithoutExpose(key); + JSObject* keyOrDelegate = markKey ? key : delegate; - // Test the value of a weakmap entry is marked black by GC if both the - // weakmap and the key are marked black. - - JS::RootedObject blackRoot2(cx); - grayRoots.grayRoot1 = nullptr; - grayRoots.grayRoot2 = nullptr; + RootedObject blackRoot1(cx); + RootedObject blackRoot2(cx); - blackRoot1 = weakMap; - blackRoot2 = key; - JS_GC(cx); - CHECK(IsMarkedBlack(weakMap)); - CHECK(IsMarkedBlack(key)); - CHECK(IsMarkedBlack(value)); + RootObject(weakMap, weakMapMarkColor, blackRoot1, grayRoots.grayRoot1); + RootObject(keyOrDelegate, keyOrDelegateMarkColor, blackRoot2, + grayRoots.grayRoot2); + + if (markOrder != 0) { + mozilla::Swap(blackRoot1.get(), blackRoot2.get()); + mozilla::Swap(grayRoots.grayRoot1, grayRoots.grayRoot2); + } - blackRoot1 = key; - blackRoot2 = weakMap; - JS_GC(cx); - CHECK(IsMarkedBlack(weakMap)); - CHECK(IsMarkedBlack(key)); - CHECK(IsMarkedBlack(value)); + JS_GC(cx); - // Test that a weakmap key is marked gray if it has a gray delegate and the - // map is either gray or black. + ClearGrayRoots(); - JSObject* delegate = UncheckedUnwrapWithoutExpose(key); - blackRoot1 = weakMap; - blackRoot2 = nullptr; - grayRoots.grayRoot1 = delegate; - grayRoots.grayRoot2 = nullptr; - JS_GC(cx); - CHECK(IsMarkedGray(delegate)); - CHECK(IsMarkedGray(key)); - CHECK(IsMarkedBlack(weakMap)); - CHECK(IsMarkedGray(value)); + CHECK(GetCellColor(weakMap) == weakMapMarkColor); + CHECK(GetCellColor(keyOrDelegate) == keyOrDelegateMarkColor); + CHECK(GetCellColor(value) == expectedValueColor); + } + + return true; +} + +#ifdef JS_GC_ZEAL - blackRoot1 = nullptr; - blackRoot2 = nullptr; - grayRoots.grayRoot1 = weakMap; - grayRoots.grayRoot2 = delegate; - JS_GC(cx); - CHECK(IsMarkedGray(delegate)); - CHECK(IsMarkedGray(key)); - CHECK(IsMarkedGray(weakMap)); - CHECK(IsMarkedGray(value)); +bool TestJSWeakMapWithGrayUnmarking(MarkKeyOrDelegate markKey, + CellColor weakMapMarkColor, + CellColor keyOrDelegateMarkColor, + CellColor expectedValueColor) { + // This is like the previous test, but things are marked black by gray + // unmarking during incremental GC. - // Test that a weakmap key is marked gray if it has a black delegate but - // the map is gray. + JSObject* weakMap; + JSObject* key; + JSObject* value; - blackRoot1 = delegate; - blackRoot2 = nullptr; - grayRoots.grayRoot1 = weakMap; - grayRoots.grayRoot2 = nullptr; - JS_GC(cx); - CHECK(IsMarkedBlack(delegate)); - CHECK(IsMarkedGray(key)); - CHECK(IsMarkedGray(weakMap)); - CHECK(IsMarkedGray(value)); + // If both map and key are marked the same color, test both possible + // orderings. + unsigned markOrderings = weakMapMarkColor == keyOrDelegateMarkColor ? 2 : 1; + + JS_SetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking), 0); - blackRoot1 = delegate; - blackRoot2 = nullptr; - grayRoots.grayRoot1 = weakMap; - grayRoots.grayRoot2 = key; - JS_GC(cx); - CHECK(IsMarkedBlack(delegate)); - CHECK(IsMarkedGray(key)); - CHECK(IsMarkedGray(weakMap)); - CHECK(IsMarkedGray(value)); + for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) { + CHECK(CreateJSWeakMapObjects(&weakMap, &key, &value)); - // Test that a weakmap key is marked black if it has a black delegate and - // the map is black. + JSObject* delegate = UncheckedUnwrapWithoutExpose(key); + JSObject* keyOrDelegate = markKey ? key : delegate; + + grayRoots.grayRoot1 = keyOrDelegate; + grayRoots.grayRoot2 = weakMap; - blackRoot1 = delegate; - blackRoot2 = weakMap; - grayRoots.grayRoot1 = nullptr; - grayRoots.grayRoot2 = nullptr; - JS_GC(cx); - CHECK(IsMarkedBlack(delegate)); - CHECK(IsMarkedBlack(key)); - CHECK(IsMarkedBlack(weakMap)); - CHECK(IsMarkedBlack(value)); - - blackRoot1 = delegate; - blackRoot2 = weakMap; - grayRoots.grayRoot1 = key; - grayRoots.grayRoot2 = nullptr; - JS_GC(cx); - CHECK(IsMarkedBlack(delegate)); - CHECK(IsMarkedBlack(key)); - CHECK(IsMarkedBlack(weakMap)); - CHECK(IsMarkedBlack(value)); - - // Test what happens if there is a delegate but it is not marked for both - // black and gray cases. + // Start an incremental GC and run until gray roots have been pushed onto + // the mark stack. + JS::PrepareForFullGC(cx); + JS::StartIncrementalGC(cx, GC_NORMAL, JS::GCReason::DEBUG_GC, 1000000); + MOZ_ASSERT(cx->runtime()->gc.state() == gc::State::Sweep); + MOZ_ASSERT(cx->zone()->gcState() == Zone::MarkBlackAndGray); - delegate = nullptr; - blackRoot1 = key; - blackRoot2 = weakMap; - grayRoots.grayRoot1 = nullptr; - grayRoots.grayRoot2 = nullptr; - JS_GC(cx); - CHECK(IsMarkedBlack(key)); - CHECK(IsMarkedBlack(weakMap)); - CHECK(IsMarkedBlack(value)); + // Unmark gray things as specified. + if (markOrder != 0) { + MaybeExposeObject(weakMap, weakMapMarkColor); + MaybeExposeObject(keyOrDelegate, keyOrDelegateMarkColor); + } else { + MaybeExposeObject(keyOrDelegate, keyOrDelegateMarkColor); + MaybeExposeObject(weakMap, weakMapMarkColor); + } - blackRoot1 = nullptr; - blackRoot2 = nullptr; - grayRoots.grayRoot1 = weakMap; - grayRoots.grayRoot2 = key; - JS_GC(cx); - CHECK(IsMarkedGray(key)); - CHECK(IsMarkedGray(weakMap)); - CHECK(IsMarkedGray(value)); + JS::FinishIncrementalGC(cx, JS::GCReason::API); + + ClearGrayRoots(); - blackRoot1 = nullptr; - blackRoot2 = nullptr; - grayRoots.grayRoot1 = nullptr; - grayRoots.grayRoot2 = nullptr; + CHECK(GetCellColor(weakMap) == weakMapMarkColor); + CHECK(GetCellColor(keyOrDelegate) == keyOrDelegateMarkColor); + CHECK(GetCellColor(value) == expectedValueColor); + } + + JS_UnsetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking)); return true; } -bool TestUnassociatedWeakMaps() { - // Make a weakmap that's not associated with a JSObject. - auto weakMap = cx->make_unique<GCManagedObjectWeakMap>(cx); +static void MaybeExposeObject(JSObject* object, CellColor color) { + if (color == CellColor::Black) { + JS::ExposeObjectToActiveJS(object); + } +} + +#endif // JS_GC_ZEAL + +bool CreateJSWeakMapObjects(JSObject** weakMapOut, JSObject** keyOut, + JSObject** valueOut) { + RootedObject key(cx, AllocWeakmapKeyObject()); + CHECK(key); + + RootedObject value(cx, AllocPlainObject()); + CHECK(value); + + RootedObject weakMap(cx, JS::NewWeakMapObject(cx)); CHECK(weakMap); - // Make sure this gets traced during GC. - Rooted<GCManagedObjectWeakMap*> rootMap(cx, weakMap.get()); + JS::RootedValue valueValue(cx, ObjectValue(*value)); + CHECK(SetWeakMapEntry(cx, weakMap, key, valueValue)); + + *weakMapOut = weakMap; + *keyOut = key; + *valueOut = value; + return true; +} + +bool TestInternalWeakMap(CellColor keyMarkColor, CellColor delegateMarkColor, + CellColor expectedColor) { + // Test marking for internal weakmaps (without an owning JSObject). + // + // All of the key, delegate and value are expected to end up the same color. + + UniquePtr<GCManagedObjectWeakMap> weakMap; + JSObject* key; + JSObject* value; + + // If both key and delegate are marked the same color, test both possible + // orderings. + unsigned markOrderings = keyMarkColor == delegateMarkColor ? 2 : 1; + + for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) { + CHECK(CreateInternalWeakMapObjects(&weakMap, &key, &value)); + + JSObject* delegate = UncheckedUnwrapWithoutExpose(key); + + RootedObject blackRoot1(cx); + RootedObject blackRoot2(cx); + + Rooted<GCManagedObjectWeakMap*> rootMap(cx, weakMap.get()); + RootObject(key, keyMarkColor, blackRoot1, grayRoots.grayRoot1); + RootObject(delegate, delegateMarkColor, blackRoot2, grayRoots.grayRoot2); + + if (markOrder != 0) { + mozilla::Swap(blackRoot1.get(), blackRoot2.get()); + mozilla::Swap(grayRoots.grayRoot1, grayRoots.grayRoot2); + } + + JS_GC(cx); + + ClearGrayRoots(); + + CHECK(GetCellColor(key) == expectedColor); + CHECK(GetCellColor(delegate) == expectedColor); + CHECK(GetCellColor(value) == expectedColor); + } + + return true; +} + +#ifdef JS_GC_ZEAL - JSObject* key = AllocWeakmapKeyObject(); +bool TestInternalWeakMapWithGrayUnmarking(CellColor keyMarkColor, + CellColor delegateMarkColor, + CellColor expectedColor) { + UniquePtr<GCManagedObjectWeakMap> weakMap; + JSObject* key; + JSObject* value; + + // If both key and delegate are marked the same color, test both possible + // orderings. + unsigned markOrderings = keyMarkColor == delegateMarkColor ? 2 : 1; + + JS_SetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking), 0); + + for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) { + CHECK(CreateInternalWeakMapObjects(&weakMap, &key, &value)); + + JSObject* delegate = UncheckedUnwrapWithoutExpose(key); + + Rooted<GCManagedObjectWeakMap*> rootMap(cx, weakMap.get()); + grayRoots.grayRoot1 = key; + grayRoots.grayRoot2 = delegate; + + // Start an incremental GC and run until gray roots have been pushed onto + // the mark stack. + JS::PrepareForFullGC(cx); + JS::StartIncrementalGC(cx, GC_NORMAL, JS::GCReason::DEBUG_GC, 1000000); + MOZ_ASSERT(cx->runtime()->gc.state() == gc::State::Sweep); + MOZ_ASSERT(cx->zone()->gcState() == Zone::MarkBlackAndGray); + + // Unmark gray things as specified. + if (markOrder != 0) { + MaybeExposeObject(key, keyMarkColor); + MaybeExposeObject(delegate, delegateMarkColor); + } else { + MaybeExposeObject(key, keyMarkColor); + MaybeExposeObject(delegate, delegateMarkColor); + } + + JS::FinishIncrementalGC(cx, JS::GCReason::API); + + ClearGrayRoots(); + + CHECK(GetCellColor(key) == expectedColor); + CHECK(GetCellColor(delegate) == expectedColor); + CHECK(GetCellColor(value) == expectedColor); + } + + JS_UnsetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking)); + + return true; +} + +#endif // JS_GC_ZEAL + +bool CreateInternalWeakMapObjects(UniquePtr<GCManagedObjectWeakMap>* weakMapOut, + JSObject** keyOut, JSObject** valueOut) { + RootedObject key(cx, AllocWeakmapKeyObject()); CHECK(key); - JSObject* value = AllocPlainObject(); + RootedObject value(cx, AllocPlainObject()); CHECK(value); + auto weakMap = cx->make_unique<GCManagedObjectWeakMap>(cx); + CHECK(weakMap); + CHECK(weakMap->add(cx, key, value)); - // Test the value of a weakmap entry is marked gray by GC if the - // key is marked gray. - - grayRoots.grayRoot1 = key; - JS_GC(cx); - CHECK(IsMarkedGray(key)); - CHECK(IsMarkedGray(value)); - - // Test the value of a weakmap entry is marked gray by GC if the key is marked - // gray. - - grayRoots.grayRoot1 = key; - grayRoots.grayRoot2 = nullptr; - JS_GC(cx); - CHECK(IsMarkedGray(key)); - CHECK(IsMarkedGray(value)); - - // Test the value of a weakmap entry is marked black by GC if the key is - // marked black. - - JS::RootedObject blackRoot(cx); - blackRoot = key; - grayRoots.grayRoot1 = nullptr; - grayRoots.grayRoot2 = nullptr; - JS_GC(cx); - CHECK(IsMarkedBlack(key)); - CHECK(IsMarkedBlack(value)); - - // Test that a weakmap key is marked gray if it has a gray delegate. - - JSObject* delegate = UncheckedUnwrapWithoutExpose(key); - blackRoot = nullptr; - grayRoots.grayRoot1 = delegate; - grayRoots.grayRoot2 = nullptr; - JS_GC(cx); - CHECK(IsMarkedGray(delegate)); - CHECK(IsMarkedGray(key)); - CHECK(IsMarkedGray(value)); + *weakMapOut = std::move(weakMap); + *keyOut = key; + *valueOut = value; + return true; +} - // Test that a weakmap key is marked black if it has a black delegate. - - blackRoot = delegate; - grayRoots.grayRoot1 = nullptr; - grayRoots.grayRoot2 = nullptr; - JS_GC(cx); - CHECK(IsMarkedBlack(delegate)); - CHECK(IsMarkedBlack(key)); - CHECK(IsMarkedBlack(value)); - - blackRoot = delegate; - grayRoots.grayRoot1 = key; - grayRoots.grayRoot2 = nullptr; - JS_GC(cx); - CHECK(IsMarkedBlack(delegate)); - CHECK(IsMarkedBlack(key)); - CHECK(IsMarkedBlack(value)); - - // Test what happens if there is a delegate but it is not marked for both - // black and gray cases. - - delegate = nullptr; - blackRoot = key; - grayRoots.grayRoot1 = nullptr; - grayRoots.grayRoot2 = nullptr; - JS_GC(cx); - CHECK(IsMarkedBlack(key)); - CHECK(IsMarkedBlack(value)); - - blackRoot = nullptr; - grayRoots.grayRoot1 = key; - grayRoots.grayRoot2 = nullptr; - JS_GC(cx); - CHECK(IsMarkedGray(key)); - CHECK(IsMarkedGray(value)); - - blackRoot = nullptr; - grayRoots.grayRoot1 = nullptr; - grayRoots.grayRoot2 = nullptr; - - return true; +void RootObject(JSObject* object, CellColor color, RootedObject& blackRoot, + JS::Heap<JSObject*>& grayRoot) { + if (color == CellColor::Black) { + blackRoot = object; + } else if (color == CellColor::Gray) { + grayRoot = object; + } else { + MOZ_RELEASE_ASSERT(color == CellColor::White); + } } bool TestCCWs() { JSObject* target = AllocPlainObject(); CHECK(target); // Test getting a new wrapper doesn't return a gray wrapper. @@ -589,25 +631,28 @@ bool InitGlobals() { global1.init(cx, global); if (!createGlobal()) { return false; } global2.init(cx, global); return global2 != nullptr; } -void InitGrayRootTracer() { +void ClearGrayRoots() { grayRoots.grayRoot1 = nullptr; grayRoots.grayRoot2 = nullptr; +} + +void InitGrayRootTracer() { + ClearGrayRoots(); JS_SetGrayGCRootsTracer(cx, TraceGrayRoots, &grayRoots); } void RemoveGrayRootTracer() { - grayRoots.grayRoot1 = nullptr; - grayRoots.grayRoot2 = nullptr; + ClearGrayRoots(); JS_SetGrayGCRootsTracer(cx, nullptr, nullptr); } static void TraceGrayRoots(JSTracer* trc, void* data) { auto grayRoots = static_cast<GrayRoots*>(data); TraceEdge(trc, &grayRoots->grayRoot1, "gray root 1"); TraceEdge(trc, &grayRoots->grayRoot2, "gray root 2"); }
deleted file mode 100644 --- a/js/src/jsapi-tests/testGCWeakRef.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* -*- 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/. */ - -#include "gc/Barrier.h" -#include "js/RootingAPI.h" - -#include "jsapi-tests/tests.h" - -struct MyHeap { - explicit MyHeap(JSObject* obj) : weak(obj) {} - js::WeakHeapPtrObject weak; - - void trace(JSTracer* trc) { js::TraceWeakEdge(trc, &weak, "weak"); } -}; - -BEGIN_TEST(testGCWeakRef) { - // 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<MyHeap> heap(cx, MyHeap(obj)); - obj = nullptr; - - cx->runtime()->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; - JS::RootedValue v(cx); - 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)
--- a/js/src/jsutil.h +++ b/js/src/jsutil.h @@ -247,16 +247,17 @@ const uint8_t JS_FRESH_NURSERY_PATTERN = const uint8_t JS_SWEPT_NURSERY_PATTERN = 0x2B; const uint8_t JS_ALLOCATED_NURSERY_PATTERN = 0x2D; const uint8_t JS_FRESH_TENURED_PATTERN = 0x4F; const uint8_t JS_MOVED_TENURED_PATTERN = 0x49; const uint8_t JS_SWEPT_TENURED_PATTERN = 0x4B; const uint8_t JS_ALLOCATED_TENURED_PATTERN = 0x4D; const uint8_t JS_FREED_HEAP_PTR_PATTERN = 0x6B; const uint8_t JS_FREED_CHUNK_PATTERN = 0x8B; +const uint8_t JS_FREED_ARENA_PATTERN = 0x9B; const uint8_t JS_SWEPT_TI_PATTERN = 0x6F; const uint8_t JS_FRESH_MARK_STACK_PATTERN = 0x9F; /* * Ensure JS_SWEPT_CODE_PATTERN is a byte pattern that will crash immediately * when executed, so either an undefined instruction or an instruction that's * illegal in user mode. */
--- a/js/src/vm/JSScript.h +++ b/js/src/vm/JSScript.h @@ -64,29 +64,34 @@ struct IonScriptCounts; #define ION_PENDING_SCRIPT ((js::jit::IonScript*)0x3) #define BASELINE_DISABLED_SCRIPT ((js::jit::BaselineScript*)0x1) class AutoKeepTypeScripts; class AutoSweepTypeScript; class BreakpointSite; class Debugger; +class GCParallelTask; class LazyScript; class ModuleObject; class RegExpObject; class SourceCompressionTask; class Shape; class TypeScript; namespace frontend { struct BytecodeEmitter; class FunctionBox; class ModuleSharedContext; } // namespace frontend +namespace gc { +void SweepLazyScripts(GCParallelTask* task); +} // namespace gc + namespace detail { // Do not call this directly! It is exposed for the friend declarations in // this file. JSScript* CopyScript(JSContext* cx, HandleScript src, HandleScriptSourceObject sourceObject, MutableHandle<GCVector<Scope*>> scopes); @@ -3020,16 +3025,17 @@ class alignas(uintptr_t) LazyScriptData // Information about a script which may be (or has been) lazily compiled to // bytecode from its source. class LazyScript : public gc::TenuredCell { // If non-nullptr, the script has been compiled and this is a forwarding // pointer to the result. This is a weak pointer: after relazification, we // can collect the script if there are no other pointers to it. WeakHeapPtrScript script_; + friend void js::gc::SweepLazyScripts(GCParallelTask* task); // Original function with which the lazy script is associated. GCPtrFunction function_; // This field holds one of: // * LazyScript in which the script is nested. This case happens if the // enclosing script is lazily parsed and have never been compiled. //
--- a/js/src/wasm/WasmInstance.cpp +++ b/js/src/wasm/WasmInstance.cpp @@ -1407,18 +1407,17 @@ bool Instance::memoryAccessInBounds(uint } return true; } void Instance::tracePrivate(JSTracer* trc) { // This method is only called from WasmInstanceObject so the only reason why // TraceEdge is called is so that the pointer can be updated during a moving - // GC. TraceWeakEdge may sound better, but it is less efficient given that - // we know object_ is already marked. + // GC. MOZ_ASSERT(!gc::IsAboutToBeFinalized(&object_)); TraceEdge(trc, &object_, "wasm instance object"); // OK to just do one tier here; though the tiers have different funcImports // tables, they share the tls object. for (const FuncImport& fi : metadata(code().stableTier()).funcImports) { TraceNullableEdge(trc, &funcImportTls(fi).fun, "wasm import"); }
--- a/js/src/wasm/WasmTable.cpp +++ b/js/src/wasm/WasmTable.cpp @@ -77,18 +77,17 @@ SharedTable Table::create(JSContext* cx, MOZ_CRASH(); } } void Table::tracePrivate(JSTracer* trc) { // If this table has a WasmTableObject, then this method is only called by // WasmTableObject's trace hook so maybeObject_ must already be marked. // TraceEdge is called so that the pointer can be updated during a moving - // GC. TraceWeakEdge may sound better, but it is less efficient given that - // we know object_ is already marked. + // GC. if (maybeObject_) { MOZ_ASSERT(!gc::IsAboutToBeFinalized(&maybeObject_)); TraceEdge(trc, &maybeObject_, "wasm table object"); } switch (kind_) { case TableKind::FuncRef: { for (uint32_t i = 0; i < length_; i++) {
--- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -3619,18 +3619,17 @@ nsDocumentViewer::PrintPreview(nsIPrintS mAutoBeforeAndAfterPrint = std::move(autoBeforeAndAfterPrint); } dom::Element* root = doc->GetRootElement(); if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdisallowselectionprint)) { PR_PL(("PrintPreview: found mozdisallowselectionprint")); printJob->SetDisallowSelectionPrint(true); } - rv = printJob->PrintPreview(aPrintSettings, aChildDOMWin, - aWebProgressListener); + rv = printJob->PrintPreview(doc, aPrintSettings, aWebProgressListener); mPrintPreviewZoomed = false; if (NS_FAILED(rv)) { OnDonePrinting(); } return rv; # else return NS_ERROR_FAILURE; # endif
--- a/layout/printing/nsPrintJob.cpp +++ b/layout/printing/nsPrintJob.cpp @@ -529,27 +529,32 @@ void nsPrintJob::Destroy() { void nsPrintJob::DestroyPrintingData() { mPrt = nullptr; } //--------------------------------------------------------------------------------- //-- Section: Methods needed by the DocViewer //--------------------------------------------------------------------------------- //-------------------------------------------------------- nsresult nsPrintJob::Initialize(nsIDocumentViewerPrint* aDocViewerPrint, - nsIDocShell* aContainer, Document* aDocument, + nsIDocShell* aDocShell, Document* aOriginalDoc, float aScreenDPI) { NS_ENSURE_ARG_POINTER(aDocViewerPrint); - NS_ENSURE_ARG_POINTER(aContainer); - NS_ENSURE_ARG_POINTER(aDocument); + NS_ENSURE_ARG_POINTER(aDocShell); + NS_ENSURE_ARG_POINTER(aOriginalDoc); mDocViewerPrint = aDocViewerPrint; - mContainer = do_GetWeakReference(aContainer); - mDocument = aDocument; + mDocShell = do_GetWeakReference(aDocShell); mScreenDPI = aScreenDPI; + // XXX We should not be storing this. The original document that the user + // selected to print can be mutated while print preview is open. Anything + // we need to know about the original document should be checked and stored + // here instead. + mOriginalDoc = aOriginalDoc; + return NS_OK; } //------------------------------------------------------- bool nsPrintJob::CheckBeforeDestroy() { if (mPrt && mPrt->mPreparingForPrint) { mPrt->mDocWasToBeDestroyed = true; return true; @@ -567,17 +572,17 @@ nsresult nsPrintJob::Cancelled() { //------------------------------------------------------- // Install our event listeners on the document to prevent // some events from being processed while in PrintPreview // // No return code - if this fails, there isn't much we can do void nsPrintJob::SuppressPrintPreviewUserEvents() { if (!mPrt->mPPEventSuppressor) { - nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mContainer); + nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell); if (!docShell) { return; } if (nsPIDOMWindowOuter* win = docShell->GetWindow()) { nsCOMPtr<EventTarget> target = win->GetFrameElementInternal(); mPrt->mPPEventSuppressor = new PrintPreviewUserEventSuppressor(target); } @@ -620,24 +625,24 @@ static void DumpLayoutData(const char* a FILE* aFD); #endif //-------------------------------------------------------------------------------- nsresult nsPrintJob::CommonPrint(bool aIsPrintPreview, nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aWebProgressListener, - Document* aDoc) { + Document* aSourceDoc) { // Callers must hold a strong reference to |this| to ensure that we stay // alive for the duration of this method, because our main owning reference // (on nsDocumentViewer) might be cleared during this function (if we cause // script to run and it cancels the print operation). nsresult rv = DoCommonPrint(aIsPrintPreview, aPrintSettings, - aWebProgressListener, aDoc); + aWebProgressListener, aSourceDoc); if (NS_FAILED(rv)) { if (aIsPrintPreview) { mIsCreatingPrintPreview = false; SetIsPrintPreview(false); } else { SetIsPrinting(false); } if (mProgressDialogIsShown) CloseProgressDialog(aWebProgressListener); @@ -648,17 +653,17 @@ nsresult nsPrintJob::CommonPrint(bool aI } return rv; } nsresult nsPrintJob::DoCommonPrint(bool aIsPrintPreview, nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aWebProgressListener, - Document* aDoc) { + Document* aSourceDoc) { nsresult rv; if (aIsPrintPreview) { // The WebProgressListener can be QI'ed to nsIPrintingPromptService // then that means the progress dialog is already being shown. nsCOMPtr<nsIPrintingPromptService> pps( do_QueryInterface(aWebProgressListener)); mProgressDialogIsShown = pps != nullptr; @@ -733,39 +738,39 @@ nsresult nsPrintJob::DoCommonPrint(bool // because the Print Dialog will "steal" focus and later when you try // to get the currently focused windows it will be nullptr printData->mCurrentFocusWin = FindFocusedDOMWindow(); // Check to see if there is a "regular" selection bool isSelection = IsThereARangeSelection(printData->mCurrentFocusWin); // Get the docshell for this documentviewer - nsCOMPtr<nsIDocShell> webContainer(do_QueryReferent(mContainer, &rv)); + nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell, &rv)); NS_ENSURE_SUCCESS(rv, rv); { if (aIsPrintPreview) { nsCOMPtr<nsIContentViewer> viewer; - webContainer->GetContentViewer(getter_AddRefs(viewer)); + docShell->GetContentViewer(getter_AddRefs(viewer)); if (viewer && viewer->GetDocument() && viewer->GetDocument()->IsShowing()) { viewer->GetDocument()->OnPageHide(false, nullptr); } } nsAutoScriptBlocker scriptBlocker; printData->mPrintObject = MakeUnique<nsPrintObject>(); - rv = printData->mPrintObject->Init(webContainer, aDoc, aIsPrintPreview); + rv = printData->mPrintObject->Init(docShell, aSourceDoc, aIsPrintPreview); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE( printData->mPrintDocList.AppendElement(printData->mPrintObject.get()), NS_ERROR_OUT_OF_MEMORY); - printData->mIsParentAFrameSet = IsParentAFrameSet(webContainer); + printData->mIsParentAFrameSet = IsParentAFrameSet(docShell); printData->mPrintObject->mFrameType = printData->mIsParentAFrameSet ? eFrameSet : eDoc; // Build the "tree" of PrintObjects BuildDocTree(printData->mPrintObject->mDocShell, &printData->mPrintDocList, printData->mPrintObject); } @@ -786,17 +791,17 @@ nsresult nsPrintJob::DoCommonPrint(bool !printData->mPrintObject->mDocument->GetRootElement()) return NS_ERROR_GFX_PRINTER_STARTDOC; // Create the linkage from the sub-docs back to the content element // in the parent document MapContentToWebShells(printData->mPrintObject, printData->mPrintObject); printData->mIsIFrameSelected = IsThereAnIFrameSelected( - webContainer, printData->mCurrentFocusWin, printData->mIsParentAFrameSet); + docShell, printData->mCurrentFocusWin, printData->mIsParentAFrameSet); // Setup print options for UI if (printData->mIsParentAFrameSet) { if (printData->mCurrentFocusWin) { printData->mPrintSettings->SetHowToEnableFrameUI( nsIPrintSettings::kFrameEnableAll); } else { printData->mPrintSettings->SetHowToEnableFrameUI( @@ -842,17 +847,17 @@ nsresult nsPrintJob::DoCommonPrint(bool // and get a remote print job, but the parent won't display a prompt. if (!printSilently || printingViaParent) { nsCOMPtr<nsIPrintingPromptService> printPromptService( do_GetService(kPrintingPromptService)); if (printPromptService) { nsPIDOMWindowOuter* domWin = nullptr; // We leave domWin as nullptr to indicate a call for print preview. if (!aIsPrintPreview) { - domWin = mDocument->GetWindow(); + domWin = mOriginalDoc->GetWindow(); NS_ENSURE_TRUE(domWin, NS_ERROR_FAILURE); } // Platforms not implementing a given dialog for the service may // return NS_ERROR_NOT_IMPLEMENTED or an error code. // // NS_ERROR_NOT_IMPLEMENTED indicates they want default behavior // Any other error code means we must bail out @@ -1030,64 +1035,58 @@ nsresult nsPrintJob::DoCommonPrint(bool //--------------------------------------------------------------------------------- nsresult nsPrintJob::Print(nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aWebProgressListener) { // If we have a print preview document, use that instead of the original // mDocument. That way animated images etc. get printed using the same state // as in print preview. Document* doc = mPrtPreview && mPrtPreview->mPrintObject ? mPrtPreview->mPrintObject->mDocument - : mDocument; + : mOriginalDoc; return CommonPrint(false, aPrintSettings, aWebProgressListener, doc); } nsresult nsPrintJob::PrintPreview( - nsIPrintSettings* aPrintSettings, mozIDOMWindowProxy* aChildDOMWin, + Document* aSourceDoc, nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aWebProgressListener) { // Get the DocShell and see if it is busy // (We can't Print Preview this document if it is still busy) - nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mContainer)); + nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell)); NS_ENSURE_STATE(docShell); auto busyFlags = docShell->GetBusyFlags(); if (busyFlags != nsIDocShell::BUSY_FLAGS_NONE) { CloseProgressDialog(aWebProgressListener); FirePrintingErrorEvent(NS_ERROR_GFX_PRINTER_DOC_IS_BUSY); return NS_ERROR_FAILURE; } - auto* window = nsPIDOMWindowOuter::From(aChildDOMWin); - NS_ENSURE_STATE(window); - nsCOMPtr<Document> doc = window->GetDoc(); - NS_ENSURE_STATE(doc); - // Document is not busy -- go ahead with the Print Preview - return CommonPrint(true, aPrintSettings, aWebProgressListener, doc); + return CommonPrint(true, aPrintSettings, aWebProgressListener, aSourceDoc); } //---------------------------------------------------------------------------------- bool nsPrintJob::IsFramesetDocument() const { - nsCOMPtr<nsIDocShell> webContainer(do_QueryReferent(mContainer)); - return IsParentAFrameSet(webContainer); + nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell)); + return IsParentAFrameSet(docShell); } //---------------------------------------------------------------------------------- bool nsPrintJob::IsIFrameSelected() { // Get the docshell for this documentviewer - nsCOMPtr<nsIDocShell> webContainer(do_QueryReferent(mContainer)); + nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell)); // Get the currently focused window nsCOMPtr<nsPIDOMWindowOuter> currentFocusWin = FindFocusedDOMWindow(); - if (currentFocusWin && webContainer) { + if (currentFocusWin && docShell) { // Get whether the doc contains a frameset // Also, check to see if the currently focus docshell // is a child of this docshell bool isParentFrameSet; - return IsThereAnIFrameSelected(webContainer, currentFocusWin, - isParentFrameSet); + return IsThereAnIFrameSelected(docShell, currentFocusWin, isParentFrameSet); } return false; } //---------------------------------------------------------------------------------- bool nsPrintJob::IsRangeSelection() { // Get the currently focused window nsCOMPtr<nsPIDOMWindowOuter> currentFocusWin = FindFocusedDOMWindow(); @@ -1210,17 +1209,17 @@ void nsPrintJob::ShowPrintProgress(bool } // Now open the service to get the progress dialog // If we don't get a service, that's ok, then just don't show progress if (showProgresssDialog) { nsCOMPtr<nsIPrintingPromptService> printPromptService( do_GetService(kPrintingPromptService)); if (printPromptService) { - nsPIDOMWindowOuter* domWin = mDocument->GetWindow(); + nsPIDOMWindowOuter* domWin = mOriginalDoc->GetWindow(); if (!domWin) return; nsCOMPtr<nsIDocShell> docShell = domWin->GetDocShell(); if (!docShell) return; nsCOMPtr<nsIDocShellTreeOwner> owner; docShell->GetTreeOwner(getter_AddRefs(owner)); nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(owner); if (!browserChrome) return; @@ -2612,24 +2611,24 @@ static bool DocHasPrintCallbackCanvas(Do } /** * Checks to see if the document this print engine is associated with has any * canvases that have a mozPrintCallback. * https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement#Properties */ bool nsPrintJob::HasPrintCallbackCanvas() { - if (!mDocument) { + if (!mOriginalDoc) { return false; } - // First check this mDocument. + // First check this mOriginalDoc. bool result = false; - DocHasPrintCallbackCanvas(mDocument, static_cast<void*>(&result)); + DocHasPrintCallbackCanvas(mOriginalDoc, static_cast<void*>(&result)); // Also check the sub documents. - return result || DocHasPrintCallbackCanvas(mDocument); + return result || DocHasPrintCallbackCanvas(mOriginalDoc); } //------------------------------------------------------- bool nsPrintJob::PrePrintPage() { NS_ASSERTION(mPageSeqFrame.IsAlive(), "mPageSeqFrame is not alive!"); NS_ASSERTION(mPrt, "mPrt is null!"); // Although these should NEVER be nullptr @@ -2818,17 +2817,17 @@ void nsPrintJob::CleanupDocTitleArray(ch /** --------------------------------------------------- * Get the Focused Frame for a documentviewer */ already_AddRefed<nsPIDOMWindowOuter> nsPrintJob::FindFocusedDOMWindow() const { nsIFocusManager* fm = nsFocusManager::GetFocusManager(); NS_ENSURE_TRUE(fm, nullptr); - nsPIDOMWindowOuter* window = mDocument->GetWindow(); + nsPIDOMWindowOuter* window = mOriginalDoc->GetWindow(); NS_ENSURE_TRUE(window, nullptr); nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot(); NS_ENSURE_TRUE(rootWindow, nullptr); nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; nsFocusManager::GetFocusedDescendant(rootWindow, nsFocusManager::eIncludeAllDescendants, @@ -2847,17 +2846,17 @@ bool nsPrintJob::IsWindowsInOurSubTree(n bool found = false; // now check to make sure it is in "our" tree of docshells if (window) { nsCOMPtr<nsIDocShell> docShell = window->GetDocShell(); if (docShell) { // get this DocViewer docshell - nsCOMPtr<nsIDocShell> thisDVDocShell(do_QueryReferent(mContainer)); + nsCOMPtr<nsIDocShell> thisDVDocShell(do_QueryReferent(mDocShell)); while (!found) { if (docShell) { if (docShell == thisDVDocShell) { found = true; break; } } else { break; // at top of tree @@ -3165,17 +3164,16 @@ void nsPrintJob::TurnScriptingOn(bool aD // The following for loop uses nsPrintObject instances that are owned by // mPrt or mPrtPreview. Therefore, this method needs to guarantee that // they won't be deleted in this method. RefPtr<nsPrintData> printData = mPrt ? mPrt : mPrtPreview; if (!printData) { return; } - NS_ASSERTION(mDocument, "We MUST have a document."); // First, get the script global object from the document... for (uint32_t i = 0; i < printData->mPrintDocList.Length(); i++) { nsPrintObject* po = printData->mPrintDocList.ElementAt(i); MOZ_ASSERT(po); Document* doc = po->mDocument; if (!doc) {
--- a/layout/printing/nsPrintJob.h +++ b/layout/printing/nsPrintJob.h @@ -54,22 +54,67 @@ class nsPrintJob final : public nsIObser // nsISupports interface... NS_DECL_ISUPPORTS // nsIObserver NS_DECL_NSIOBSERVER NS_DECL_NSIWEBPROGRESSLISTENER - // Our nsIWebBrowserPrint implementation defers to these methods. + /** + * Initialize for printing, or for creating a print preview document. + * + * aDocViewerPrint owns us. + * + * When called in preparation for printing, aOriginalDoc is aDocViewerPrint's + * document. The document/viewer may be for a sub-document (an iframe). + * + * When called in preparation for print preview, aOriginalDoc belongs to a + * different docViewer, in a different docShell, in a different TabGroup. + * In this case our aDocViewerPrint is the docViewer for the about:blank + * document in a new tab that the Firefox frontend code has created in + * preparation for PrintPreview to generate a print preview document in it. + * + * NOTE: In the case we're called for print preview, aOriginalDoc actually + * may not be the original document that the user selected to print. It + * is not the actual original document in the case when the user chooses to + * display a simplified version of a print preview document. In that + * instance the Firefox frontend code creates a second print preview tab, + * with a new docViewer and nsPrintJob, and passes the previous print preview + * document as aOriginalDoc (it doesn't want to pass the actual original + * document since it may have mutated)! + */ + nsresult Initialize(nsIDocumentViewerPrint* aDocViewerPrint, + nsIDocShell* aDocShell, + mozilla::dom::Document* aOriginalDoc, float aScreenDPI); + + // Our nsIWebBrowserPrint implementation (nsDocumentViewer) defers to the + // following methods. + + /** + * May be called immediately after initialization, or after one or more + * PrintPreview calls. + */ nsresult Print(nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aWebProgressListener); - nsresult PrintPreview(nsIPrintSettings* aPrintSettings, - mozIDOMWindowProxy* aChildDOMWin, + + /** + * Generates a new print preview document and replaces our docViewer's + * document with it. (Note that this breaks the normal invariant that a + * Document and its nsDocumentViewer have an unchanging 1:1 relationship.) + * + * This may be called multiple times on the same instance in order to + * recreate the print preview document to take account of settings that the + * user has changed in the print preview interface. In this case aSourceDoc + * is actually our docViewer's current document! + */ + nsresult PrintPreview(mozilla::dom::Document* aSourceDoc, + nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aWebProgressListener); + bool IsDoingPrint() const { return mIsDoingPrinting; } bool IsDoingPrintPreview() const { return mIsDoingPrintPreview; } bool IsFramesetDocument() const; bool IsIFrameSelected(); bool IsRangeSelection(); bool IsFramesetFrameSelected() const; /// If the returned value is not greater than zero, an error occurred. int32_t GetPrintPreviewNumPages(); @@ -79,20 +124,16 @@ class nsPrintJob final : public nsIObser // This enum tells indicates what the default should be for the title // if the title from the document is null enum eDocTitleDefault { eDocTitleDefBlank, eDocTitleDefURLDoc }; void Destroy(); void DestroyPrintingData(); - nsresult Initialize(nsIDocumentViewerPrint* aDocViewerPrint, - nsIDocShell* aContainer, - mozilla::dom::Document* aDocument, float aScreenDPI); - nsresult GetSeqFrameAndCountPages(nsIFrame*& aSeqFrame, int32_t& aCount); // // The following three methods are used for printing... // nsresult DocumentReadyForPrinting(); nsresult GetSelectionDocument(nsIDeviceContextSpec* aDevSpec, mozilla::dom::Document** aNewDoc); @@ -191,21 +232,21 @@ class nsPrintJob final : public nsIObser private: nsPrintJob& operator=(const nsPrintJob& aOther) = delete; ~nsPrintJob(); nsresult CommonPrint(bool aIsPrintPreview, nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aWebProgressListener, - mozilla::dom::Document* aDoc); + mozilla::dom::Document* aSourceDoc); nsresult DoCommonPrint(bool aIsPrintPreview, nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aWebProgressListener, - mozilla::dom::Document* aDoc); + mozilla::dom::Document* aSourceDoc); void FirePrintCompletionEvent(); void DisconnectPagePrintTimer(); /** * This method is called to resume printing after all outstanding resources * referenced by the static clone have finished loading. (It is possibly @@ -226,20 +267,27 @@ class nsPrintJob final : public nsIObser nsresult ReconstructAndReflow(bool aDoSetPixelScale); nsresult UpdateSelectionAndShrinkPrintObject(nsPrintObject* aPO, bool aDocumentIsTopLevel); nsresult InitPrintDocConstruction(bool aHandleError); void FirePrintPreviewUpdateEvent(); void PageDone(nsresult aResult); - RefPtr<mozilla::dom::Document> mDocument; + // The document that we were originally created for in order to print it or + // create a print preview of it. This may belong to mDocViewerPrint or may + // belong to a different docViewer in a different docShell. In reality, this + // also may not be the original document that the user selected to print (see + // the comment documenting Initialize() above). + RefPtr<mozilla::dom::Document> mOriginalDoc; + + // The docViewer that owns us, and its docShell. nsCOMPtr<nsIDocumentViewerPrint> mDocViewerPrint; + nsWeakPtr mDocShell; - nsWeakPtr mContainer; WeakFrame mPageSeqFrame; // We are the primary owner of our nsPrintData member vars. These vars // are refcounted so that functions (e.g. nsPrintData methods) can create // temporary owning references when they need to fire a callback that // could conceivably destroy this nsPrintJob owner object and all its // member-data. RefPtr<nsPrintData> mPrt;