Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sts=4 et sw=4 tw=99:
3 : * This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "mozilla/DebugOnly.h"
8 : #include "mozilla/IntegerPrintfMacros.h"
9 : #include "mozilla/Sprintf.h"
10 :
11 : #ifdef MOZ_VALGRIND
12 : # include <valgrind/memcheck.h>
13 : #endif
14 :
15 : #include "gc/GCInternals.h"
16 : #include "gc/PublicIterators.h"
17 : #include "gc/Zone.h"
18 : #include "js/HashTable.h"
19 : #include "vm/JSContext.h"
20 :
21 : #include "gc/ArenaList-inl.h"
22 : #include "gc/GC-inl.h"
23 : #include "gc/Marking-inl.h"
24 : #include "vm/JSContext-inl.h"
25 :
26 : using namespace js;
27 : using namespace js::gc;
28 :
29 : #ifdef JS_GC_ZEAL
30 :
31 : /*
32 : * Write barrier verification
33 : *
34 : * The next few functions are for write barrier verification.
35 : *
36 : * The VerifyBarriers function is a shorthand. It checks if a verification phase
37 : * is currently running. If not, it starts one. Otherwise, it ends the current
38 : * phase and starts a new one.
39 : *
40 : * The user can adjust the frequency of verifications, which causes
41 : * VerifyBarriers to be a no-op all but one out of N calls. However, if the
42 : * |always| parameter is true, it starts a new phase no matter what.
43 : *
44 : * Pre-Barrier Verifier:
45 : * When StartVerifyBarriers is called, a snapshot is taken of all objects in
46 : * the GC heap and saved in an explicit graph data structure. Later,
47 : * EndVerifyBarriers traverses the heap again. Any pointer values that were in
48 : * the snapshot and are no longer found must be marked; otherwise an assertion
49 : * triggers. Note that we must not GC in between starting and finishing a
50 : * verification phase.
51 : */
52 :
53 : struct EdgeValue
54 : {
55 : void* thing;
56 : JS::TraceKind kind;
57 : const char* label;
58 : };
59 :
60 : struct VerifyNode
61 : {
62 : void* thing;
63 : JS::TraceKind kind;
64 : uint32_t count;
65 : EdgeValue edges[1];
66 : };
67 :
68 : typedef HashMap<void*, VerifyNode*, DefaultHasher<void*>, SystemAllocPolicy> NodeMap;
69 :
70 : /*
71 : * The verifier data structures are simple. The entire graph is stored in a
72 : * single block of memory. At the beginning is a VerifyNode for the root
73 : * node. It is followed by a sequence of EdgeValues--the exact number is given
74 : * in the node. After the edges come more nodes and their edges.
75 : *
76 : * The edgeptr and term fields are used to allocate out of the block of memory
77 : * for the graph. If we run out of memory (i.e., if edgeptr goes beyond term),
78 : * we just abandon the verification.
79 : *
80 : * The nodemap field is a hashtable that maps from the address of the GC thing
81 : * to the VerifyNode that represents it.
82 : */
83 : class js::VerifyPreTracer final : public JS::CallbackTracer
84 : {
85 : JS::AutoDisableGenerationalGC noggc;
86 :
87 : void onChild(const JS::GCCellPtr& thing) override;
88 :
89 : public:
90 : /* The gcNumber when the verification began. */
91 : uint64_t number;
92 :
93 : /* This counts up to gcZealFrequency to decide whether to verify. */
94 : int count;
95 :
96 : /* This graph represents the initial GC "snapshot". */
97 : VerifyNode* curnode;
98 : VerifyNode* root;
99 : char* edgeptr;
100 : char* term;
101 : NodeMap nodemap;
102 :
103 0 : explicit VerifyPreTracer(JSRuntime* rt)
104 0 : : JS::CallbackTracer(rt), noggc(rt->mainContextFromOwnThread()), number(rt->gc.gcNumber()),
105 0 : count(0), curnode(nullptr), root(nullptr), edgeptr(nullptr), term(nullptr)
106 0 : {}
107 :
108 0 : ~VerifyPreTracer() {
109 0 : js_free(root);
110 0 : }
111 : };
112 :
113 : /*
114 : * This function builds up the heap snapshot by adding edges to the current
115 : * node.
116 : */
117 : void
118 0 : VerifyPreTracer::onChild(const JS::GCCellPtr& thing)
119 : {
120 0 : MOZ_ASSERT(!IsInsideNursery(thing.asCell()));
121 :
122 : // Skip things in other runtimes.
123 0 : if (thing.asCell()->asTenured().runtimeFromAnyThread() != runtime())
124 : return;
125 :
126 0 : edgeptr += sizeof(EdgeValue);
127 0 : if (edgeptr >= term) {
128 0 : edgeptr = term;
129 0 : return;
130 : }
131 :
132 0 : VerifyNode* node = curnode;
133 0 : uint32_t i = node->count;
134 :
135 0 : node->edges[i].thing = thing.asCell();
136 0 : node->edges[i].kind = thing.kind();
137 0 : node->edges[i].label = contextName();
138 0 : node->count++;
139 : }
140 :
141 : static VerifyNode*
142 0 : MakeNode(VerifyPreTracer* trc, void* thing, JS::TraceKind kind)
143 : {
144 0 : NodeMap::AddPtr p = trc->nodemap.lookupForAdd(thing);
145 0 : if (!p) {
146 0 : VerifyNode* node = (VerifyNode*)trc->edgeptr;
147 0 : trc->edgeptr += sizeof(VerifyNode) - sizeof(EdgeValue);
148 0 : if (trc->edgeptr >= trc->term) {
149 0 : trc->edgeptr = trc->term;
150 0 : return nullptr;
151 : }
152 :
153 0 : node->thing = thing;
154 0 : node->count = 0;
155 0 : node->kind = kind;
156 0 : if (!trc->nodemap.add(p, thing, node)) {
157 0 : trc->edgeptr = trc->term;
158 0 : return nullptr;
159 : }
160 :
161 0 : return node;
162 : }
163 : return nullptr;
164 : }
165 :
166 : static VerifyNode*
167 : NextNode(VerifyNode* node)
168 : {
169 0 : if (node->count == 0)
170 0 : return (VerifyNode*)((char*)node + sizeof(VerifyNode) - sizeof(EdgeValue));
171 : else
172 : return (VerifyNode*)((char*)node + sizeof(VerifyNode) +
173 0 : sizeof(EdgeValue)*(node->count - 1));
174 : }
175 :
176 : void
177 0 : gc::GCRuntime::startVerifyPreBarriers()
178 : {
179 0 : if (verifyPreData || isIncrementalGCInProgress())
180 0 : return;
181 :
182 0 : JSContext* cx = rt->mainContextFromOwnThread();
183 0 : if (temporaryAbortIfWasmGc(cx))
184 : return;
185 :
186 0 : if (IsIncrementalGCUnsafe(rt) != AbortReason::None ||
187 0 : rt->hasHelperThreadZones())
188 : {
189 : return;
190 : }
191 :
192 0 : number++;
193 :
194 0 : VerifyPreTracer* trc = js_new<VerifyPreTracer>(rt);
195 0 : if (!trc)
196 : return;
197 :
198 0 : AutoPrepareForTracing prep(cx);
199 :
200 : {
201 0 : AutoLockGC lock(cx->runtime());
202 0 : for (auto chunk = allNonEmptyChunks(lock); !chunk.done(); chunk.next())
203 0 : chunk->bitmap.clear();
204 : }
205 :
206 0 : gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::TRACE_HEAP);
207 :
208 0 : const size_t size = 64 * 1024 * 1024;
209 0 : trc->root = (VerifyNode*)js_malloc(size);
210 0 : if (!trc->root)
211 : goto oom;
212 0 : trc->edgeptr = (char*)trc->root;
213 0 : trc->term = trc->edgeptr + size;
214 :
215 0 : if (!trc->nodemap.init())
216 : goto oom;
217 :
218 : /* Create the root node. */
219 0 : trc->curnode = MakeNode(trc, nullptr, JS::TraceKind(0));
220 :
221 0 : incrementalState = State::MarkRoots;
222 :
223 : /* Make all the roots be edges emanating from the root node. */
224 0 : traceRuntime(trc, prep.session());
225 :
226 : VerifyNode* node;
227 0 : node = trc->curnode;
228 0 : if (trc->edgeptr == trc->term)
229 : goto oom;
230 :
231 : /* For each edge, make a node for it if one doesn't already exist. */
232 0 : while ((char*)node < trc->edgeptr) {
233 0 : for (uint32_t i = 0; i < node->count; i++) {
234 0 : EdgeValue& e = node->edges[i];
235 0 : VerifyNode* child = MakeNode(trc, e.thing, e.kind);
236 0 : if (child) {
237 0 : trc->curnode = child;
238 0 : js::TraceChildren(trc, e.thing, e.kind);
239 : }
240 0 : if (trc->edgeptr == trc->term)
241 : goto oom;
242 : }
243 :
244 : node = NextNode(node);
245 : }
246 :
247 0 : verifyPreData = trc;
248 0 : incrementalState = State::Mark;
249 0 : marker.start();
250 :
251 0 : for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
252 0 : MOZ_ASSERT(!zone->usedByHelperThread());
253 0 : zone->setNeedsIncrementalBarrier(true);
254 0 : zone->arenas.clearFreeLists();
255 : }
256 :
257 0 : return;
258 :
259 : oom:
260 0 : incrementalState = State::NotActive;
261 0 : js_delete(trc);
262 0 : verifyPreData = nullptr;
263 : }
264 :
265 : static bool
266 : IsMarkedOrAllocated(TenuredCell* cell)
267 : {
268 0 : return cell->isMarkedAny();
269 : }
270 :
271 : struct CheckEdgeTracer : public JS::CallbackTracer {
272 : VerifyNode* node;
273 0 : explicit CheckEdgeTracer(JSRuntime* rt) : JS::CallbackTracer(rt), node(nullptr) {}
274 : void onChild(const JS::GCCellPtr& thing) override;
275 : };
276 :
277 : static const uint32_t MAX_VERIFIER_EDGES = 1000;
278 :
279 : /*
280 : * This function is called by EndVerifyBarriers for every heap edge. If the edge
281 : * already existed in the original snapshot, we "cancel it out" by overwriting
282 : * it with nullptr. EndVerifyBarriers later asserts that the remaining
283 : * non-nullptr edges (i.e., the ones from the original snapshot that must have
284 : * been modified) must point to marked objects.
285 : */
286 : void
287 0 : CheckEdgeTracer::onChild(const JS::GCCellPtr& thing)
288 : {
289 : // Skip things in other runtimes.
290 0 : if (thing.asCell()->asTenured().runtimeFromAnyThread() != runtime())
291 : return;
292 :
293 : /* Avoid n^2 behavior. */
294 0 : if (node->count > MAX_VERIFIER_EDGES)
295 : return;
296 :
297 0 : for (uint32_t i = 0; i < node->count; i++) {
298 0 : if (node->edges[i].thing == thing.asCell()) {
299 0 : MOZ_ASSERT(node->edges[i].kind == thing.kind());
300 0 : node->edges[i].thing = nullptr;
301 0 : return;
302 : }
303 : }
304 : }
305 :
306 : void
307 643 : js::gc::AssertSafeToSkipBarrier(TenuredCell* thing)
308 : {
309 1929 : mozilla::DebugOnly<Zone*> zone = thing->zoneFromAnyThread();
310 0 : MOZ_ASSERT(!zone->needsIncrementalBarrier() || zone->isAtomsZone());
311 0 : }
312 :
313 : static bool
314 0 : IsMarkedOrAllocated(const EdgeValue& edge)
315 : {
316 0 : if (!edge.thing || IsMarkedOrAllocated(TenuredCell::fromPointer(edge.thing)))
317 : return true;
318 :
319 : // Permanent atoms and well-known symbols aren't marked during graph traversal.
320 0 : if (edge.kind == JS::TraceKind::String && static_cast<JSString*>(edge.thing)->isPermanentAtom())
321 : return true;
322 0 : if (edge.kind == JS::TraceKind::Symbol && static_cast<JS::Symbol*>(edge.thing)->isWellKnownSymbol())
323 : return true;
324 :
325 : return false;
326 : }
327 :
328 : void
329 0 : gc::GCRuntime::endVerifyPreBarriers()
330 : {
331 0 : VerifyPreTracer* trc = verifyPreData;
332 :
333 0 : if (!trc)
334 0 : return;
335 :
336 0 : MOZ_ASSERT(!JS::IsGenerationalGCEnabled(rt));
337 :
338 0 : AutoPrepareForTracing prep(rt->mainContextFromOwnThread());
339 :
340 0 : bool compartmentCreated = false;
341 :
342 : /* We need to disable barriers before tracing, which may invoke barriers. */
343 0 : for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
344 0 : if (!zone->needsIncrementalBarrier())
345 0 : compartmentCreated = true;
346 :
347 0 : zone->setNeedsIncrementalBarrier(false);
348 : }
349 :
350 : /*
351 : * We need to bump gcNumber so that the methodjit knows that jitcode has
352 : * been discarded.
353 : */
354 0 : MOZ_ASSERT(trc->number == number);
355 0 : number++;
356 :
357 0 : verifyPreData = nullptr;
358 0 : incrementalState = State::NotActive;
359 :
360 0 : if (!compartmentCreated &&
361 0 : IsIncrementalGCUnsafe(rt) == AbortReason::None &&
362 0 : !rt->hasHelperThreadZones())
363 : {
364 0 : CheckEdgeTracer cetrc(rt);
365 :
366 : /* Start after the roots. */
367 0 : VerifyNode* node = NextNode(trc->root);
368 0 : while ((char*)node < trc->edgeptr) {
369 0 : cetrc.node = node;
370 0 : js::TraceChildren(&cetrc, node->thing, node->kind);
371 :
372 0 : if (node->count <= MAX_VERIFIER_EDGES) {
373 0 : for (uint32_t i = 0; i < node->count; i++) {
374 0 : EdgeValue& edge = node->edges[i];
375 0 : if (!IsMarkedOrAllocated(edge)) {
376 : char msgbuf[1024];
377 0 : SprintfLiteral(msgbuf,
378 : "[barrier verifier] Unmarked edge: %s %p '%s' edge to %s %p",
379 : JS::GCTraceKindToAscii(node->kind), node->thing,
380 : edge.label,
381 0 : JS::GCTraceKindToAscii(edge.kind), edge.thing);
382 0 : MOZ_ReportAssertionFailure(msgbuf, __FILE__, __LINE__);
383 0 : MOZ_CRASH();
384 : }
385 : }
386 : }
387 :
388 : node = NextNode(node);
389 : }
390 : }
391 :
392 0 : marker.reset();
393 0 : marker.stop();
394 :
395 0 : js_delete(trc);
396 : }
397 :
398 : /*** Barrier Verifier Scheduling ***/
399 :
400 : void
401 0 : gc::GCRuntime::verifyPreBarriers()
402 : {
403 0 : if (verifyPreData)
404 0 : endVerifyPreBarriers();
405 : else
406 0 : startVerifyPreBarriers();
407 0 : }
408 :
409 : void
410 0 : gc::VerifyBarriers(JSRuntime* rt, VerifierType type)
411 : {
412 0 : if (GCRuntime::temporaryAbortIfWasmGc(rt->mainContextFromOwnThread()))
413 : return;
414 0 : if (type == PreBarrierVerifier)
415 0 : rt->gc.verifyPreBarriers();
416 : }
417 :
418 : void
419 606282 : gc::GCRuntime::maybeVerifyPreBarriers(bool always)
420 : {
421 0 : if (!hasZealMode(ZealMode::VerifierPre))
422 : return;
423 :
424 0 : if (rt->mainContextFromOwnThread()->suppressGC)
425 : return;
426 :
427 0 : if (verifyPreData) {
428 0 : if (++verifyPreData->count < zealFrequency && !always)
429 : return;
430 :
431 0 : endVerifyPreBarriers();
432 : }
433 :
434 0 : startVerifyPreBarriers();
435 : }
436 :
437 : void
438 606280 : js::gc::MaybeVerifyBarriers(JSContext* cx, bool always)
439 : {
440 0 : GCRuntime* gc = &cx->runtime()->gc;
441 606280 : gc->maybeVerifyPreBarriers(always);
442 0 : }
443 :
444 : void
445 0 : js::gc::GCRuntime::finishVerifier()
446 : {
447 0 : if (verifyPreData) {
448 0 : js_delete(verifyPreData.ref());
449 0 : verifyPreData = nullptr;
450 : }
451 0 : }
452 :
453 : #endif /* JS_GC_ZEAL */
454 :
455 : #if defined(JSGC_HASH_TABLE_CHECKS) || defined(DEBUG)
456 :
457 0 : class HeapCheckTracerBase : public JS::CallbackTracer
458 : {
459 : public:
460 : explicit HeapCheckTracerBase(JSRuntime* rt, WeakMapTraceKind weakTraceKind);
461 : bool init();
462 : bool traceHeap(AutoTraceSession& session);
463 : virtual void checkCell(Cell* cell) = 0;
464 :
465 : protected:
466 : void dumpCellInfo(Cell* cell);
467 : void dumpCellPath();
468 :
469 : Cell* parentCell() {
470 0 : return parentIndex == -1 ? nullptr : stack[parentIndex].thing.asCell();
471 : }
472 :
473 : size_t failures;
474 :
475 : private:
476 : void onChild(const JS::GCCellPtr& thing) override;
477 :
478 : struct WorkItem {
479 : WorkItem(JS::GCCellPtr thing, const char* name, int parentIndex)
480 0 : : thing(thing), name(name), parentIndex(parentIndex), processed(false)
481 : {}
482 :
483 : JS::GCCellPtr thing;
484 : const char* name;
485 : int parentIndex;
486 : bool processed;
487 : };
488 :
489 : JSRuntime* rt;
490 : bool oom;
491 : HashSet<Cell*, DefaultHasher<Cell*>, SystemAllocPolicy> visited;
492 : Vector<WorkItem, 0, SystemAllocPolicy> stack;
493 : int parentIndex;
494 : };
495 :
496 0 : HeapCheckTracerBase::HeapCheckTracerBase(JSRuntime* rt, WeakMapTraceKind weakTraceKind)
497 : : CallbackTracer(rt, weakTraceKind),
498 : failures(0),
499 : rt(rt),
500 : oom(false),
501 0 : parentIndex(-1)
502 : {
503 : #ifdef DEBUG
504 0 : setCheckEdges(false);
505 : #endif
506 0 : }
507 :
508 : bool
509 0 : HeapCheckTracerBase::init()
510 : {
511 0 : return visited.init();
512 : }
513 :
514 : void
515 0 : HeapCheckTracerBase::onChild(const JS::GCCellPtr& thing)
516 : {
517 0 : Cell* cell = thing.asCell();
518 0 : checkCell(cell);
519 :
520 0 : if (visited.lookup(cell))
521 0 : return;
522 :
523 0 : if (!visited.put(cell)) {
524 0 : oom = true;
525 0 : return;
526 : }
527 :
528 : // Don't trace into GC things owned by another runtime.
529 0 : if (cell->runtimeFromAnyThread() != rt)
530 : return;
531 :
532 : // Don't trace into GC in zones being used by helper threads.
533 : Zone* zone;
534 0 : if (thing.is<JSObject>())
535 0 : zone = thing.as<JSObject>().zone();
536 0 : else if (thing.is<JSString>())
537 0 : zone = thing.as<JSString>().zone();
538 : else
539 0 : zone = cell->asTenured().zone();
540 :
541 0 : if (zone->usedByHelperThread())
542 : return;
543 :
544 0 : WorkItem item(thing, contextName(), parentIndex);
545 0 : if (!stack.append(item))
546 0 : oom = true;
547 : }
548 :
549 : bool
550 0 : HeapCheckTracerBase::traceHeap(AutoTraceSession& session)
551 : {
552 : // The analysis thinks that traceRuntime might GC by calling a GC callback.
553 0 : JS::AutoSuppressGCAnalysis nogc;
554 0 : if (!rt->isBeingDestroyed())
555 0 : rt->gc.traceRuntime(this, session);
556 :
557 0 : while (!stack.empty() && !oom) {
558 0 : WorkItem item = stack.back();
559 0 : if (item.processed) {
560 0 : stack.popBack();
561 : } else {
562 0 : parentIndex = stack.length() - 1;
563 0 : stack.back().processed = true;
564 0 : TraceChildren(this, item.thing);
565 : }
566 : }
567 :
568 0 : return !oom;
569 : }
570 :
571 : static const char*
572 0 : GetCellColorName(Cell* cell)
573 : {
574 0 : if (cell->isMarkedBlack())
575 : return "black";
576 0 : if (cell->isMarkedGray())
577 : return "gray";
578 0 : return "white";
579 : }
580 :
581 : void
582 0 : HeapCheckTracerBase::dumpCellInfo(Cell* cell)
583 : {
584 0 : auto kind = cell->getTraceKind();
585 0 : JSObject* obj = kind == JS::TraceKind::Object ? static_cast<JSObject*>(cell) : nullptr;
586 :
587 0 : fprintf(stderr, "%s %s", GetCellColorName(cell), GCTraceKindToAscii(kind));
588 0 : if (obj)
589 0 : fprintf(stderr, " %s", obj->getClass()->name);
590 0 : fprintf(stderr, " %p", cell);
591 0 : if (obj)
592 0 : fprintf(stderr, " (compartment %p)", obj->compartment());
593 0 : }
594 :
595 : void
596 0 : HeapCheckTracerBase::dumpCellPath()
597 : {
598 0 : const char* name = contextName();
599 0 : for (int index = parentIndex; index != -1; index = stack[index].parentIndex) {
600 0 : const WorkItem& parent = stack[index];
601 0 : Cell* cell = parent.thing.asCell();
602 0 : fprintf(stderr, " from ");
603 0 : dumpCellInfo(cell);
604 0 : fprintf(stderr, " %s edge\n", name);
605 0 : name = parent.name;
606 : }
607 0 : fprintf(stderr, " from root %s\n", name);
608 0 : }
609 :
610 : #endif // defined(JSGC_HASH_TABLE_CHECKS) || defined(DEBUG)
611 :
612 : #ifdef JSGC_HASH_TABLE_CHECKS
613 :
614 0 : class CheckHeapTracer final : public HeapCheckTracerBase
615 : {
616 : public:
617 : explicit CheckHeapTracer(JSRuntime* rt);
618 : void check(AutoTraceSession& session);
619 :
620 : private:
621 : void checkCell(Cell* cell) override;
622 : };
623 :
624 0 : CheckHeapTracer::CheckHeapTracer(JSRuntime* rt)
625 0 : : HeapCheckTracerBase(rt, TraceWeakMapKeysValues)
626 0 : {}
627 :
628 : inline static bool
629 : IsValidGCThingPointer(Cell* cell)
630 : {
631 0 : return (uintptr_t(cell) & CellAlignMask) == 0;
632 : }
633 :
634 : void
635 0 : CheckHeapTracer::checkCell(Cell* cell)
636 : {
637 0 : if (!IsValidGCThingPointer(cell) || !IsGCThingValidAfterMovingGC(cell)) {
638 0 : failures++;
639 0 : fprintf(stderr, "Bad pointer %p\n", cell);
640 0 : dumpCellPath();
641 : }
642 0 : }
643 :
644 : void
645 0 : CheckHeapTracer::check(AutoTraceSession& session)
646 : {
647 0 : if (!traceHeap(session))
648 : return;
649 :
650 0 : if (failures)
651 0 : fprintf(stderr, "Heap check: %zu failure(s)\n", failures);
652 0 : MOZ_RELEASE_ASSERT(failures == 0);
653 : }
654 :
655 : void
656 0 : js::gc::CheckHeapAfterGC(JSRuntime* rt)
657 : {
658 0 : AutoTraceSession session(rt, JS::HeapState::Tracing);
659 0 : CheckHeapTracer tracer(rt);
660 0 : if (tracer.init())
661 0 : tracer.check(session);
662 0 : }
663 :
664 : #endif /* JSGC_HASH_TABLE_CHECKS */
665 :
666 : #if defined(JS_GC_ZEAL) || defined(DEBUG)
667 :
668 0 : class CheckGrayMarkingTracer final : public HeapCheckTracerBase
669 : {
670 : public:
671 : explicit CheckGrayMarkingTracer(JSRuntime* rt);
672 : bool check(AutoTraceSession& session);
673 :
674 : private:
675 : void checkCell(Cell* cell) override;
676 : };
677 :
678 0 : CheckGrayMarkingTracer::CheckGrayMarkingTracer(JSRuntime* rt)
679 0 : : HeapCheckTracerBase(rt, DoNotTraceWeakMaps)
680 : {
681 : // Weak gray->black edges are allowed.
682 0 : setTraceWeakEdges(false);
683 0 : }
684 :
685 : void
686 0 : CheckGrayMarkingTracer::checkCell(Cell* cell)
687 : {
688 0 : Cell* parent = parentCell();
689 0 : if (!parent)
690 : return;
691 :
692 0 : if (parent->isMarkedBlack() && cell->isMarkedGray()) {
693 0 : failures++;
694 :
695 0 : fprintf(stderr, "Found black to gray edge to ");
696 0 : dumpCellInfo(cell);
697 0 : fprintf(stderr, "\n");
698 0 : dumpCellPath();
699 :
700 : #ifdef DEBUG
701 0 : if (cell->is<JSObject>()) {
702 0 : fprintf(stderr, "\n");
703 0 : DumpObject(cell->as<JSObject>(), stderr);
704 : }
705 : #endif
706 : }
707 : }
708 :
709 : bool
710 0 : CheckGrayMarkingTracer::check(AutoTraceSession& session)
711 : {
712 0 : if (!traceHeap(session))
713 : return true; // Ignore failure.
714 :
715 0 : return failures == 0;
716 : }
717 :
718 : JS_FRIEND_API(bool)
719 0 : js::CheckGrayMarkingState(JSRuntime* rt)
720 : {
721 0 : MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());
722 0 : MOZ_ASSERT(!rt->gc.isIncrementalGCInProgress());
723 0 : if (!rt->gc.areGrayBitsValid())
724 : return true;
725 :
726 0 : gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP);
727 0 : AutoTraceSession session(rt, JS::HeapState::Tracing);
728 0 : CheckGrayMarkingTracer tracer(rt);
729 0 : if (!tracer.init())
730 : return true; // Ignore failure
731 :
732 : return tracer.check(session);
733 : }
734 :
735 : #endif // defined(JS_GC_ZEAL) || defined(DEBUG)
|