/* * Copyright (C) 2014-2022 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "HeapVerifier.h" #include "ButterflyInlines.h" #include "CodeBlockInlines.h" #include "JSObject.h" #include "MarkedSpaceInlines.h" #include "VMInspector.h" #include "ValueProfile.h" #include namespace JSC { HeapVerifier::HeapVerifier(Heap* heap, unsigned numberOfGCCyclesToRecord) : m_heap(heap) , m_currentCycle(0) , m_numberOfCycles(numberOfGCCyclesToRecord) { RELEASE_ASSERT(m_numberOfCycles > 0); m_cycles = makeUniqueArray(m_numberOfCycles); } const char* HeapVerifier::phaseName(HeapVerifier::Phase phase) { switch (phase) { case Phase::BeforeGC: return "BeforeGC"; case Phase::BeforeMarking: return "BeforeMarking"; case Phase::AfterMarking: return "AfterMarking"; case Phase::AfterGC: return "AfterGC"; } RELEASE_ASSERT_NOT_REACHED(); return nullptr; // Silencing a compiler warning. } void HeapVerifier::startGC() { Heap* heap = m_heap; incrementCycle(); currentCycle().reset(); currentCycle().scope = *heap->collectionScope(); currentCycle().timestamp = MonotonicTime::now(); ASSERT(!m_didPrintLogs); } void HeapVerifier::endGC() { if (m_didPrintLogs) { dataLog("END "); printVerificationHeader(); dataLog("\n\n"); m_didPrintLogs = false; } } void HeapVerifier::gatherLiveCells(HeapVerifier::Phase phase) { Heap* heap = m_heap; CellList& list = *cellListForGathering(phase); list.reset(); heap->m_objectSpace.forEachLiveCell([&list] (HeapCell* cell, HeapCell::Kind kind) { list.add({ cell, kind, CellProfile::Live }); return IterationStatus::Continue; }); } CellList* HeapVerifier::cellListForGathering(HeapVerifier::Phase phase) { switch (phase) { case Phase::BeforeMarking: return ¤tCycle().before; case Phase::AfterMarking: return ¤tCycle().after; case Phase::BeforeGC: case Phase::AfterGC: // We should not be gathering live cells during these phases. break; } RELEASE_ASSERT_NOT_REACHED(); return nullptr; // Silencing a compiler warning. } static void trimDeadCellsFromList(CellList& knownLiveSet, CellList& list) { if (!list.size()) return; for (auto& cellProfile : list.cells()) { if (cellProfile.isDead()) continue; // Don't "resurrect" known dead cells. if (!knownLiveSet.find(cellProfile.cell())) { cellProfile.setIsDead(); continue; } cellProfile.setIsLive(); } } void HeapVerifier::trimDeadCells() { CellList& knownLiveSet = currentCycle().after; trimDeadCellsFromList(knownLiveSet, currentCycle().before); for (int i = -1; i > -m_numberOfCycles; i--) { trimDeadCellsFromList(knownLiveSet, cycleForIndex(i).before); trimDeadCellsFromList(knownLiveSet, cycleForIndex(i).after); } } void HeapVerifier::printVerificationHeader() { RELEASE_ASSERT(m_heap->collectionScope()); CollectionScope scope = currentCycle().scope; MonotonicTime gcCycleTimestamp = currentCycle().timestamp; dataLog("Verifying heap in [p", getCurrentProcessID(), ", ", Thread::current(), "] vm ", RawPointer(&m_heap->vm()), " on ", scope, " GC @ ", gcCycleTimestamp, "\n"); } bool HeapVerifier::verifyCellList(Phase phase, CellList& list) { VM& vm = m_heap->vm(); auto& liveCells = list.cells(); bool listNamePrinted = false; auto printHeaderIfNeeded = scopedLambda([&] () { if (listNamePrinted) return; printVerificationHeader(); dataLog(" @ phase ", phaseName(phase), ": FAILED in cell list '", list.name(), "' (size ", liveCells.size(), ")\n"); listNamePrinted = true; m_didPrintLogs = true; }); bool success = true; for (size_t i = 0; i < liveCells.size(); i++) { CellProfile& profile = liveCells[i]; if (!profile.isLive()) continue; if (!profile.isJSCell()) continue; JSCell* cell = profile.jsCell(); success |= validateJSCell(&vm, cell, &profile, &list, printHeaderIfNeeded, " "); } return success; } bool HeapVerifier::validateCell(HeapCell* cell, VM* expectedVM) { auto printNothing = scopedLambda([] () { }); if (cell->isZapped()) { dataLog(" cell ", RawPointer(cell), " is ZAPPED\n"); return false; } if (!isJSCellKind(cell->cellKind())) return true; // Nothing more to validate. JSCell* jsCell = static_cast(cell); return validateJSCell(expectedVM, jsCell, nullptr, nullptr, printNothing); } bool HeapVerifier::validateJSCell(VM* expectedVM, JSCell* cell, CellProfile* profile, CellList* list, const ScopedLambda& printHeaderIfNeeded, const char* prefix) { auto printHeaderAndCell = [cell, profile, &printHeaderIfNeeded, prefix] () { printHeaderIfNeeded(); dataLog(prefix, "cell ", RawPointer(cell)); if (profile) dataLog(" [", profile->className(), "]"); }; // 1. Validate the cell. if (cell->isZapped()) { printHeaderAndCell(); dataLog(" is zapped\n"); return false; } StructureID structureID = cell->structureID(); if (!structureID) { printHeaderAndCell(); dataLog(" has NULL structureID\n"); return false; } if (expectedVM) { VM* cellVM = &cell->vm(); if (cellVM != expectedVM) { printHeaderAndCell(); dataLog(" is from a different VM: expected:", RawPointer(expectedVM), " actual:", RawPointer(cellVM), "\n"); return false; } // 2. Validate the cell's structure Structure* structure = structureID.decode(); if (!structure) { printHeaderAndCell(); uint32_t structureIDAsUint32 = structureID.bits(); dataLog(" with structureID ", structureIDAsUint32, " maps to a NULL Structure pointer\n"); return false; } if (structure->isZapped()) { printHeaderAndCell(); dataLog(" has ZAPPED structure ", RawPointer(structure), "\n"); return false; } if (!structure->structureID()) { printHeaderAndCell(); dataLog(" has structure ", RawPointer(structure), " whose structureID is NULL\n"); return false; } VM* structureVM = &structure->vm(); if (structureVM != expectedVM) { printHeaderAndCell(); dataLog(" has structure ", RawPointer(structure), " from a different VM: expected:", RawPointer(expectedVM), " actual:", RawPointer(structureVM), "\n"); return false; } if (list) { auto* structureProfile = list->find(structure); if (!structureProfile) { printHeaderAndCell(); dataLog(" has structure ", RawPointer(structure), " NOT found in the live cell list\n"); return false; } if (!structureProfile->isLive()) { printHeaderAndCell(); dataLog(" has DEAD structure ", RawPointer(structure), "\n"); return false; } } StructureID structureStructureID = structure->structureID(); if (!structureStructureID) { printHeaderAndCell(); dataLog(" has structure ", RawPointer(structure), " with a NULL structureID\n"); return false; } // 3. Validate the cell's structure's structure. Structure* structureStructure = structureID.decode(); if (!structureStructure) { printHeaderAndCell(); dataLog(" has structure ", RawPointer(structure), " whose structure is NULL\n"); return false; } if (structureStructure->isZapped()) { printHeaderAndCell(); dataLog(" has structure ", RawPointer(structure), " whose structure ", RawPointer(structureStructure), " is ZAPPED\n"); return false; } if (!structureStructure->structureID()) { printHeaderAndCell(); dataLog(" has structure ", RawPointer(structure), " whose structure ", RawPointer(structureStructure), " has a NULL structureID\n"); return false; } VM* structureStructureVM = &structureStructure->vm(); if (structureStructureVM != expectedVM) { printHeaderAndCell(); dataLog(" has structure ", RawPointer(structure), " whose structure ", RawPointer(structureStructure), " is from a different VM: expected:", RawPointer(expectedVM), " actual:", RawPointer(structureStructureVM), "\n"); return false; } if (list) { auto* structureStructureProfile = list->find(structureStructure); if (!structureStructureProfile) { printHeaderAndCell(); dataLog(" has structure ", RawPointer(structure), " whose structure ", RawPointer(structureStructure), " is NOT found in the live cell list\n"); return false; } if (!structureStructureProfile->isLive()) { printHeaderAndCell(); dataLog(" has structure ", RawPointer(structure), " whose structure ", RawPointer(structureStructure), " is DEAD\n"); return false; } } CodeBlock* codeBlock = jsDynamicCast(cell); if (UNLIKELY(codeBlock)) { bool success = true; codeBlock->forEachValueProfile([&](ValueProfile& valueProfile, bool) { for (unsigned i = 0; i < ValueProfile::totalNumberOfBuckets; ++i) { JSValue value = JSValue::decode(valueProfile.m_buckets[i]); if (!value) continue; if (!value.isCell()) continue; JSCell* valueCell = value.asCell(); if (valueCell->isZapped()) { printHeaderIfNeeded(); dataLog(prefix, "CodeBlock ", RawPointer(codeBlock), " has ZAPPED ValueProfile cell ", RawPointer(valueCell), "\n"); success = false; continue; } } }); if (!success) return false; } } return true; } void HeapVerifier::verify(HeapVerifier::Phase phase) { if (phase == Phase::AfterGC) { bool verified = verifyCellList(phase, currentCycle().after); RELEASE_ASSERT(verified); } } void HeapVerifier::reportCell(CellProfile& profile, int cycleIndex, HeapVerifier::GCCycle& cycle, CellList& list, const char* prefix) { HeapCell* cell = profile.cell(); VM& vm = m_heap->vm(); if (prefix) dataLog(prefix); dataLog("FOUND"); if (profile.isLive()) dataLog(" LIVE"); else if (profile.isDead()) dataLog(" DEAD"); if (!profile.isJSCell()) dataLog(" HeapCell "); else dataLog(" JSCell "); dataLog(RawPointer(cell)); if (profile.className()) dataLog(" [", profile.className(), "]"); if (profile.isLive() && profile.isJSCell()) { JSCell* jsCell = profile.jsCell(); Structure* structure = jsCell->structure(); dataLog(" structure:", RawPointer(structure)); if (jsCell->isObject()) { JSObject* obj = static_cast(cell); Butterfly* butterfly = obj->butterfly(); void* butterflyBase = butterfly->base(structure); dataLog(" butterfly:", RawPointer(butterfly), " (base:", RawPointer(butterflyBase), ")"); } } dataLog(" in ", cycle.scope, " GC[", cycleIndex, "] in '", list.name(), "' list in VM ", RawPointer(&vm), " recorded at time ", profile.timestamp(), "\n"); if (profile.stackTrace()) dataLog(*profile.stackTrace()); } void HeapVerifier::checkIfRecorded(HeapCell* cell) { bool found = false; const char* const prefix = " "; static constexpr bool verbose = true; for (int cycleIndex = 0; cycleIndex > -m_numberOfCycles; cycleIndex--) { GCCycle& cycle = cycleForIndex(cycleIndex); CellList* lists[] = { &cycle.before, &cycle.after }; if (verbose) dataLog("Checking ", cycle.scope, " GC<", cycle.timestamp, ">, cycle [", cycleIndex, "]:\n"); const char* resultPrefix = " "; for (auto* list : lists) { if (verbose) dataLog(prefix, "Cycle [", cycleIndex, "] '", list->name(), "' list: "); CellProfile* profile = list->find(cell); if (profile) { reportCell(*profile, cycleIndex, cycle, *list, resultPrefix); found = true; } else if (verbose) dataLog(resultPrefix, "cell NOT found\n"); } } if (!found) dataLog(prefix, "cell ", RawPointer(cell), " NOT FOUND\n"); } // The following are slower but more robust versions of the corresponding functions of the same name. // These robust versions are designed so that we can call them interactively from a C++ debugger // to query if a candidate is recorded cell. void HeapVerifier::checkIfRecorded(uintptr_t candidateCell) { HeapCell* candidateHeapCell = reinterpret_cast(candidateCell); auto& inspector = VMInspector::instance(); if (!inspector.getLock().tryLockWithTimeout(2_s)) { dataLog("ERROR: Timed out while waiting to iterate VMs."); return; } Locker locker { AdoptLock, inspector.getLock() }; inspector.iterate([&] (VM& vm) { if (!vm.isInService()) return IterationStatus::Continue; if (!vm.heap.m_verifier) return IterationStatus::Continue; auto* verifier = vm.heap.m_verifier.get(); dataLog("Search for cell ", RawPointer(candidateHeapCell), " in VM ", RawPointer(&vm), ":\n"); verifier->checkIfRecorded(candidateHeapCell); return IterationStatus::Continue; }); } } // namespace JSC