| /* |
| * Copyright (C) 2014-2019 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 "CodeBlockInlines.h" |
| #include "HeapIterationScope.h" |
| #include "JSCInlines.h" |
| #include "JSObject.h" |
| #include "MarkedSpaceInlines.h" |
| #include "VMInspector.h" |
| #include "ValueProfile.h" |
| #include <wtf/ProcessID.h> |
| |
| 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<GCCycle>(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<void()>([&] () { |
| 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<void()>([] () { }); |
| |
| 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<JSCell*>(cell); |
| return validateJSCell(expectedVM, jsCell, nullptr, nullptr, printNothing); |
| } |
| |
| bool HeapVerifier::validateJSCell(VM* expectedVM, JSCell* cell, CellProfile* profile, CellList* list, const ScopedLambda<void()>& 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& vm = *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 = vm.getStructure(structureID); |
| if (!structure) { |
| printHeaderAndCell(); |
| #if USE(JSVALUE64) |
| uint32_t structureIDAsUint32 = structureID; |
| #else |
| uint32_t structureIDAsUint32 = reinterpret_cast<uint32_t>(structureID); |
| #endif |
| 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 = vm.getStructure(structureID); |
| 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<CodeBlock*>(vm, 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(vm); |
| dataLog(" structure:", RawPointer(structure)); |
| if (jsCell->isObject()) { |
| JSObject* obj = static_cast<JSObject*>(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<HeapCell*>(candidateCell); |
| |
| VMInspector& inspector = VMInspector::instance(); |
| auto expectedLocker = inspector.lock(Seconds(2)); |
| if (!expectedLocker) { |
| ASSERT(expectedLocker.error() == VMInspector::Error::TimedOut); |
| dataLog("ERROR: Timed out while waiting to iterate VMs."); |
| return; |
| } |
| |
| auto& locker = expectedLocker.value(); |
| inspector.iterate(locker, [&] (VM& vm) { |
| if (!vm.heap.m_verifier) |
| return VMInspector::FunctorStatus::Continue; |
| |
| auto* verifier = vm.heap.m_verifier.get(); |
| dataLog("Search for cell ", RawPointer(candidateHeapCell), " in VM ", RawPointer(&vm), ":\n"); |
| verifier->checkIfRecorded(candidateHeapCell); |
| return VMInspector::FunctorStatus::Continue; |
| }); |
| } |
| |
| } // namespace JSC |