Changeset 213883 in webkit


Ignore:
Timestamp:
Mar 13, 2017 5:39:24 PM (7 years ago)
Author:
mark.lam@apple.com
Message:

Make the HeapVerifier useful again.
https://bugs.webkit.org/show_bug.cgi?id=161752

Reviewed by Filip Pizlo.

Resurrect the HeapVerifier. Here's what the verifier now offers:

  1. It captures the list of cells before and after GCs up to N GC cycles. N is set by JSC_numberOfGCCyclesToRecordForVerification. Currently, N defaults to 3.

This is useful if we're debugging in lldb and want to check if a candidate
cell pointer was observed by the GC during the last N GC cycles. We can do
this check buy calling HeapVerifier::checkIfRecorded() with the cell address.

HeapVerifier::checkIfRecorded() is robust and can be used on bogus addresses.
If the candidate cell was previously recorded by the HeapVerifier during a
GC cycle, checkIfRecorded() will dump any useful info it has on that cell.

  1. The HeapVerifier will verify that cells in its captured list after a GC are sane. Some examples of cell insanity are:
    • the cell claims to belong to a different VM.
    • the cell has a NULL structureID.
    • the cell has a NULL structure.
    • the cell's structure has a NULL structureID.
    • the cell's structure has a NULL structure.
    • the cell's structure's structure has a NULL structureID.
    • the cell's structure's structure has a NULL structure.

These are all signs of corruption or a GC bug. The verifier will report any
insanity it finds, and then crash with a RELEASE_ASSERT.

  1. Since the HeapVerifier captures list of cells in the heap before and after GCs for the last N GCs, it will also automatically "trim" dead cells those list after the most recent GC.

"trim" here means that the CellProfile in the HeapVerifier's lists will be
updated to reflect that the cell is now dead. It still keeps a record of the
dead cell pointer and the meta data collected about it back when it was alive.
As a result, checkIfRecorded() will also report if the candidate cell passed
to it is a dead object from a previous GC cycle.

  1. Each CellProfile captured by the HeapVerifier now track the following info:
    • the cell's HeapCell::Kind.
    • the cell's liveness.
    • if is JSCell, the cell's classInfo()->className.
    • an associated timestamp.
    • an associated stack trace.

Currently, the timestamp is only used for the time when the cell was recorded
by the HeapVerifier during GC. The stack trace is currently unused.

However, these fields are kept there so that we can instrument the VM (during
a debugging session, which requires rebuilding the VM) and record interesting
stack traces like that of the time of allocation of the cell. Since
capturing the stack traces for each cell is a very heavy weight operation,
the HeapVerifier code does not do this by default. Instead, we just leave
the building blocks for doing so in place to ease future debugging efforts.

  • heap/Heap.cpp:

(JSC::Heap::runBeginPhase):
(JSC::Heap::runEndPhase):
(JSC::Heap::didFinishCollection):

  • heap/Heap.h:

(JSC::Heap::verifier):

  • heap/MarkedAllocator.h:

(JSC::MarkedAllocator::takeLastActiveBlock): Deleted.

  • heap/MarkedSpace.h:
  • heap/MarkedSpaceInlines.h:

(JSC::MarkedSpace::forEachLiveCell):

  • tools/CellList.cpp:

(JSC::CellList::find):
(JSC::CellList::reset):
(JSC::CellList::findCell): Deleted.

  • tools/CellList.h:

(JSC::CellList::CellList):
(JSC::CellList::name):
(JSC::CellList::size):
(JSC::CellList::cells):
(JSC::CellList::add):
(JSC::CellList::reset): Deleted.

  • tools/CellProfile.h:

(JSC::CellProfile::CellProfile):
(JSC::CellProfile::cell):
(JSC::CellProfile::jsCell):
(JSC::CellProfile::isJSCell):
(JSC::CellProfile::kind):
(JSC::CellProfile::isLive):
(JSC::CellProfile::isDead):
(JSC::CellProfile::setIsLive):
(JSC::CellProfile::setIsDead):
(JSC::CellProfile::timestamp):
(JSC::CellProfile::className):
(JSC::CellProfile::stackTrace):
(JSC::CellProfile::setStackTrace):

  • tools/HeapVerifier.cpp:

(JSC::HeapVerifier::startGC):
(JSC::HeapVerifier::endGC):
(JSC::HeapVerifier::gatherLiveCells):
(JSC::trimDeadCellsFromList):
(JSC::HeapVerifier::trimDeadCells):
(JSC::HeapVerifier::printVerificationHeader):
(JSC::HeapVerifier::verifyCellList):
(JSC::HeapVerifier::validateCell):
(JSC::HeapVerifier::validateJSCell):
(JSC::HeapVerifier::verify):
(JSC::HeapVerifier::reportCell):
(JSC::HeapVerifier::checkIfRecorded):
(JSC::HeapVerifier::initializeGCCycle): Deleted.
(JSC::GatherCellFunctor::GatherCellFunctor): Deleted.
(JSC::GatherCellFunctor::visit): Deleted.
(JSC::GatherCellFunctor::operator()): Deleted.
(JSC::HeapVerifier::verifyButterflyIsInStorageSpace): Deleted.

  • tools/HeapVerifier.h:

(JSC::HeapVerifier::GCCycle::reset):

Location:
trunk/Source/JavaScriptCore
Files:
11 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/ChangeLog

    r213881 r213883  
     12017-03-13  Mark Lam  <mark.lam@apple.com>
     2
     3        Make the HeapVerifier useful again.
     4        https://bugs.webkit.org/show_bug.cgi?id=161752
     5
     6        Reviewed by Filip Pizlo.
     7
     8        Resurrect the HeapVerifier.  Here's what the verifier now offers:
     9
     10        1. It captures the list of cells before and after GCs up to N GC cycles.
     11           N is set by JSC_numberOfGCCyclesToRecordForVerification.
     12           Currently, N defaults to 3.
     13
     14           This is useful if we're debugging in lldb and want to check if a candidate
     15           cell pointer was observed by the GC during the last N GC cycles.  We can do
     16           this check buy calling HeapVerifier::checkIfRecorded() with the cell address.
     17
     18           HeapVerifier::checkIfRecorded() is robust and can be used on bogus addresses.
     19           If the candidate cell was previously recorded by the HeapVerifier during a
     20           GC cycle, checkIfRecorded() will dump any useful info it has on that cell.
     21
     22        2. The HeapVerifier will verify that cells in its captured list after a GC are
     23           sane.  Some examples of cell insanity are:
     24           - the cell claims to belong to a different VM.
     25           - the cell has a NULL structureID.
     26           - the cell has a NULL structure.
     27           - the cell's structure has a NULL structureID.
     28           - the cell's structure has a NULL structure.
     29           - the cell's structure's structure has a NULL structureID.
     30           - the cell's structure's structure has a NULL structure.
     31
     32           These are all signs of corruption or a GC bug.  The verifier will report any
     33           insanity it finds, and then crash with a RELEASE_ASSERT.
     34
     35        3. Since the HeapVerifier captures list of cells in the heap before and after GCs
     36           for the last N GCs, it will also automatically "trim" dead cells those list
     37           after the most recent GC.
     38
     39           "trim" here means that the CellProfile in the HeapVerifier's lists will be
     40           updated to reflect that the cell is now dead.  It still keeps a record of the
     41           dead cell pointer and the meta data collected about it back when it was alive.
     42           As a result, checkIfRecorded() will also report if the candidate cell passed
     43           to it is a dead object from a previous GC cycle.
     44
     45        4. Each CellProfile captured by the HeapVerifier now track the following info:
     46           - the cell's HeapCell::Kind.
     47           - the cell's liveness.
     48           - if is JSCell, the cell's classInfo()->className.
     49           - an associated timestamp.
     50           - an associated stack trace.
     51
     52           Currently, the timestamp is only used for the time when the cell was recorded
     53           by the HeapVerifier during GC.  The stack trace is currently unused.
     54
     55           However, these fields are kept there so that we can instrument the VM (during
     56           a debugging session, which requires rebuilding the VM) and record interesting
     57           stack traces like that of the time of allocation of the cell.  Since
     58           capturing the stack traces for each cell is a very heavy weight operation,
     59           the HeapVerifier code does not do this by default.  Instead, we just leave
     60           the building blocks for doing so in place to ease future debugging efforts.
     61
     62        * heap/Heap.cpp:
     63        (JSC::Heap::runBeginPhase):
     64        (JSC::Heap::runEndPhase):
     65        (JSC::Heap::didFinishCollection):
     66        * heap/Heap.h:
     67        (JSC::Heap::verifier):
     68        * heap/MarkedAllocator.h:
     69        (JSC::MarkedAllocator::takeLastActiveBlock): Deleted.
     70        * heap/MarkedSpace.h:
     71        * heap/MarkedSpaceInlines.h:
     72        (JSC::MarkedSpace::forEachLiveCell):
     73        * tools/CellList.cpp:
     74        (JSC::CellList::find):
     75        (JSC::CellList::reset):
     76        (JSC::CellList::findCell): Deleted.
     77        * tools/CellList.h:
     78        (JSC::CellList::CellList):
     79        (JSC::CellList::name):
     80        (JSC::CellList::size):
     81        (JSC::CellList::cells):
     82        (JSC::CellList::add):
     83        (JSC::CellList::reset): Deleted.
     84        * tools/CellProfile.h:
     85        (JSC::CellProfile::CellProfile):
     86        (JSC::CellProfile::cell):
     87        (JSC::CellProfile::jsCell):
     88        (JSC::CellProfile::isJSCell):
     89        (JSC::CellProfile::kind):
     90        (JSC::CellProfile::isLive):
     91        (JSC::CellProfile::isDead):
     92        (JSC::CellProfile::setIsLive):
     93        (JSC::CellProfile::setIsDead):
     94        (JSC::CellProfile::timestamp):
     95        (JSC::CellProfile::className):
     96        (JSC::CellProfile::stackTrace):
     97        (JSC::CellProfile::setStackTrace):
     98        * tools/HeapVerifier.cpp:
     99        (JSC::HeapVerifier::startGC):
     100        (JSC::HeapVerifier::endGC):
     101        (JSC::HeapVerifier::gatherLiveCells):
     102        (JSC::trimDeadCellsFromList):
     103        (JSC::HeapVerifier::trimDeadCells):
     104        (JSC::HeapVerifier::printVerificationHeader):
     105        (JSC::HeapVerifier::verifyCellList):
     106        (JSC::HeapVerifier::validateCell):
     107        (JSC::HeapVerifier::validateJSCell):
     108        (JSC::HeapVerifier::verify):
     109        (JSC::HeapVerifier::reportCell):
     110        (JSC::HeapVerifier::checkIfRecorded):
     111        (JSC::HeapVerifier::initializeGCCycle): Deleted.
     112        (JSC::GatherCellFunctor::GatherCellFunctor): Deleted.
     113        (JSC::GatherCellFunctor::visit): Deleted.
     114        (JSC::GatherCellFunctor::operator()): Deleted.
     115        (JSC::HeapVerifier::verifyButterflyIsInStorageSpace): Deleted.
     116        * tools/HeapVerifier.h:
     117        (JSC::HeapVerifier::GCCycle::reset):
     118
    11192017-03-13  SKumarMetro  <s.kumar@metrological.com>
    2120
  • trunk/Source/JavaScriptCore/heap/Heap.cpp

    r213675 r213883  
    10981098    willStartCollection(scope);
    10991099       
    1100     if (m_verifier) {
     1100    if (UNLIKELY(m_verifier)) {
    11011101        // Verify that live objects from the last GC cycle haven't been corrupted by
    11021102        // mutators before we begin this new GC cycle.
    11031103        m_verifier->verify(HeapVerifier::Phase::BeforeGC);
    11041104           
    1105         m_verifier->initializeGCCycle();
     1105        m_verifier->startGC();
    11061106        m_verifier->gatherLiveCells(HeapVerifier::Phase::BeforeMarking);
    11071107    }
     
    13331333    endMarking();
    13341334       
    1335     if (m_verifier) {
     1335    if (UNLIKELY(m_verifier)) {
    13361336        m_verifier->gatherLiveCells(HeapVerifier::Phase::AfterMarking);
    13371337        m_verifier->verify(HeapVerifier::Phase::AfterMarking);
     
    13581358    updateAllocationLimits();
    13591359
    1360     didFinishCollection();
    1361    
    1362     if (m_verifier) {
     1360    if (UNLIKELY(m_verifier)) {
    13631361        m_verifier->trimDeadCells();
    13641362        m_verifier->verify(HeapVerifier::Phase::AfterGC);
    13651363    }
     1364
     1365    didFinishCollection();
    13661366
    13671367    if (false) {
     
    21632163    }
    21642164
     2165    if (UNLIKELY(m_verifier))
     2166        m_verifier->endGC();
     2167
    21652168    RELEASE_ASSERT(m_collectionScope);
    21662169    m_lastCollectionScope = m_collectionScope;
  • trunk/Source/JavaScriptCore/heap/Heap.h

    r213652 r213883  
    352352    JS_EXPORT_PRIVATE void setRunLoop(CFRunLoopRef);
    353353#endif // USE(CF)
    354    
     354
     355    HeapVerifier* verifier() const { return m_verifier.get(); }
     356
    355357private:
    356358    friend class AllocatingScope;
  • trunk/Source/JavaScriptCore/heap/MarkedAllocator.h

    r210844 r213883  
    4343    macro(live, Live) /* The set of block indices that have actual blocks. */\
    4444    macro(empty, Empty) /* The set of all blocks that have no live objects and nothing to destroy. */ \
    45     macro(allocated, Allocated) /* The set of allblocks that are full of live objects. */\
     45    macro(allocated, Allocated) /* The set of all blocks that are full of live objects. */\
    4646    macro(canAllocateButNotEmpty, CanAllocateButNotEmpty) /* The set of all blocks are neither empty nor retired (i.e. are more than minMarkedBlockUtilization full). */ \
    4747    macro(eden, Eden) /* The set of all blocks that have new objects since the last GC. */\
     
    156156    void* tryAllocate(GCDeferralContext* = nullptr);
    157157    Heap* heap() { return m_heap; }
    158     MarkedBlock::Handle* takeLastActiveBlock()
    159     {
    160         MarkedBlock::Handle* block = m_lastActiveBlock;
    161         m_lastActiveBlock = 0;
    162         return block;
    163     }
    164    
     158
    165159    template<typename Functor> void forEachBlock(const Functor&);
    166160    template<typename Functor> void forEachNotEmptyBlock(const Functor&);
  • trunk/Source/JavaScriptCore/heap/MarkedSpace.h

    r210844 r213883  
    179179    void* tryAllocateSlow(Subspace&, GCDeferralContext*, size_t);
    180180
     181    // Use this version when calling from within the GC where we know that the allocators
     182    // have already been stopped.
     183    template<typename Functor> void forEachLiveCell(const Functor&);
     184
    181185    static void initializeSizeClassForStepSize();
    182186   
     
    213217    MarkedAllocator* m_lastAllocator { nullptr };
    214218    MarkedAllocator* m_allocatorForEmptyAllocation { nullptr };
     219
     220    friend class HeapVerifier;
    215221};
    216222
  • trunk/Source/JavaScriptCore/heap/MarkedSpaceInlines.h

    r206154 r213883  
    11/*
    2  * Copyright (C) 2016 Apple Inc. All rights reserved.
     2 * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    3434{
    3535    ASSERT(isIterating());
     36    forEachLiveCell(functor);
     37}
     38
     39template<typename Functor> inline void MarkedSpace::forEachLiveCell(const Functor& functor)
     40{
    3641    BlockIterator end = m_blocks.set().end();
    3742    for (BlockIterator it = m_blocks.set().begin(); it != end; ++it) {
  • trunk/Source/JavaScriptCore/tools/CellList.cpp

    r213675 r213883  
    2929namespace JSC {
    3030
    31 CellProfile* CellList::findCell(JSCell* cell)
     31CellProfile* CellList::find(HeapCell* cell)
    3232{
    33     for (auto& profile : liveCells) {
    34         if (cell == profile.cell)
    35             return &profile;
     33    if (!size())
     34        return nullptr;
     35
     36    if (!m_mapIsUpToDate) {
     37        m_map.clear();
     38        for (auto& profile : m_cells)
     39            m_map.add(profile.cell(), &profile);
     40        m_mapIsUpToDate = true;
    3641    }
    37     return nullptr;
     42    return m_map.get(cell);
     43}
     44
     45void CellList::reset()
     46{
     47    m_cells.clear();
     48    m_map.clear();
     49    m_mapIsUpToDate = false;
    3850}
    3951
  • trunk/Source/JavaScriptCore/tools/CellList.h

    r213675 r213883  
    2727
    2828#include "CellProfile.h"
    29 #include <wtf/Vector.h>
     29#include <wtf/HashMap.h>
     30#include <wtf/SegmentedVector.h>
    3031
    3132namespace JSC {
    3233
    33 struct CellList {
     34class CellList {
     35    WTF_MAKE_FAST_ALLOCATED;
     36public:
    3437    CellList(const char* name)
    35         : name(name)
    36         , hasLiveCells(true)
     38        : m_name(name)
    3739    {
    3840    }
    3941   
    40     void reset()
     42    const char* name() const { return m_name; }
     43    size_t size() const { return m_cells.size(); }
     44
     45    typedef SegmentedVector<CellProfile, 64> CellProfileVector;
     46    CellProfileVector& cells() { return m_cells; }
     47
     48    void add(CellProfile&& profile)
    4149    {
    42         liveCells.clear();
    43         hasLiveCells = true; // Presume to have live objects until the list is trimmed.
     50        m_cells.append(WTFMove(profile));
     51        m_mapIsUpToDate = false;
    4452    }
    45    
    46     CellProfile* findCell(JSCell*);
    47    
    48     const char* name;
    49     Vector<CellProfile> liveCells;
    50     bool hasLiveCells;
     53
     54    void reset();
     55
     56    CellProfile* find(HeapCell*);
     57
     58private:
     59    const char* m_name;
     60    CellProfileVector m_cells;
     61
     62    bool m_mapIsUpToDate { false };
     63    HashMap<HeapCell*, CellProfile*> m_map;
    5164};
    5265   
  • trunk/Source/JavaScriptCore/tools/CellProfile.h

    r213675 r213883  
    2626#pragma once
    2727
     28#include "JSCell.h"
     29#include "StackTrace.h"
     30#include "Structure.h"
     31#include <wtf/MonotonicTime.h>
     32
    2833namespace JSC {
    2934
    30 class JSCell;
     35struct CellProfile {
     36    enum Liveness {
     37        Unknown,
     38        Dead,
     39        Live
     40    };
    3141
    32 struct CellProfile {
    33     CellProfile(JSCell* cell, bool isConfirmedDead = false)
    34         : cell(cell)
    35         , isConfirmedDead(isConfirmedDead)
     42    CellProfile(HeapCell* cell, HeapCell::Kind kind, Liveness liveness)
     43        : m_cell(cell)
     44        , m_kind(kind)
     45        , m_liveness(liveness)
     46        , m_timestamp(MonotonicTime::now())
    3647    {
     48        if (m_kind == HeapCell::JSCell && m_liveness != Dead)
     49            m_className = jsCell()->structure()->classInfo()->className;
    3750    }
     51
     52    CellProfile(CellProfile&& other)
     53        : m_cell(other.m_cell)
     54        , m_kind(other.m_kind)
     55        , m_liveness(other.m_liveness)
     56        , m_timestamp(other.m_timestamp)
     57        , m_className(other.m_className)
     58        , m_stackTrace(WTFMove(other.m_stackTrace))
     59    { }
     60
     61    HeapCell* cell() const { return m_cell; }
     62    JSCell* jsCell() const
     63    {
     64        ASSERT(isJSCell());
     65        return static_cast<JSCell*>(m_cell);
     66    }
     67
     68    bool isJSCell() const { return m_kind == HeapCell::JSCell; }
    3869   
    39     JSCell* cell;
    40     bool isConfirmedDead;
     70    HeapCell::Kind kind() const { return m_kind; }
     71
     72    bool isLive() const { return m_liveness == Live; }
     73    bool isDead() const { return m_liveness == Dead; }
     74
     75    void setIsLive() { m_liveness = Live; }
     76    void setIsDead() { m_liveness = Dead; }
     77
     78    MonotonicTime timestamp() const { return m_timestamp; }
     79
     80    const char* className() const { return m_className; }
     81
     82    StackTrace* stackTrace() const { return m_stackTrace.get(); }
     83    void setStackTrace(StackTrace* trace) { m_stackTrace = std::unique_ptr<StackTrace>(trace); }
     84
     85private:
     86    HeapCell* m_cell;
     87    HeapCell::Kind m_kind;
     88    Liveness m_liveness { Unknown };
     89    MonotonicTime m_timestamp;
     90    const char* m_className { nullptr };
     91    std::unique_ptr<StackTrace> m_stackTrace;
    4192};
    4293
  • trunk/Source/JavaScriptCore/tools/HeapVerifier.cpp

    r213675 r213883  
    11/*
    2  * Copyright (C) 2014, 2016 Apple Inc. All rights reserved.
     2 * Copyright (C) 2014-2017 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    2727#include "HeapVerifier.h"
    2828
    29 #include "ButterflyInlines.h"
     29#include "CodeBlock.h"
    3030#include "HeapIterationScope.h"
    3131#include "JSCInlines.h"
    3232#include "JSObject.h"
    3333#include "MarkedSpaceInlines.h"
     34#include "VMInspector.h"
     35#include "ValueProfile.h"
     36#include <wtf/ProcessID.h>
    3437
    3538namespace JSC {
     
    6063}
    6164
    62 void HeapVerifier::initializeGCCycle()
     65void HeapVerifier::startGC()
    6366{
    6467    Heap* heap = m_heap;
    6568    incrementCycle();
     69    currentCycle().reset();
    6670    currentCycle().scope = *heap->collectionScope();
    67 }
    68 
    69 struct GatherCellFunctor : MarkedBlock::CountFunctor {
    70     GatherCellFunctor(CellList& list)
    71         : m_list(list)
    72     {
    73         ASSERT(!list.liveCells.size());
    74     }
    75 
    76     inline void visit(JSCell* cell)
    77     {
    78         CellProfile profile(cell);
    79         m_list.liveCells.append(profile);
    80     }
    81 
    82     IterationStatus operator()(HeapCell* cell, HeapCell::Kind kind) const
    83     {
    84         if (kind == HeapCell::JSCell) {
    85             // FIXME: This const_cast exists because this isn't a C++ lambda.
    86             // https://bugs.webkit.org/show_bug.cgi?id=159644
    87             const_cast<GatherCellFunctor*>(this)->visit(static_cast<JSCell*>(cell));
    88         }
    89         return IterationStatus::Continue;
    90     }
    91 
    92     CellList& m_list;
    93 };
     71    currentCycle().timestamp = MonotonicTime::now();
     72    ASSERT(!m_didPrintLogs);
     73}
     74
     75void HeapVerifier::endGC()
     76{
     77    if (m_didPrintLogs) {
     78        dataLog("END ");
     79        printVerificationHeader();
     80        dataLog("\n\n");
     81        m_didPrintLogs = false;
     82    }
     83}
    9484
    9585void HeapVerifier::gatherLiveCells(HeapVerifier::Phase phase)
     
    9888    CellList& list = *cellListForGathering(phase);
    9989
    100     HeapIterationScope iterationScope(*heap);
    10190    list.reset();
    102     GatherCellFunctor functor(list);
    103     heap->m_objectSpace.forEachLiveCell(iterationScope, functor);
     91    heap->m_objectSpace.forEachLiveCell([&list] (HeapCell* cell, HeapCell::Kind kind) {
     92        list.add({ cell, kind, CellProfile::Live });
     93        return IterationStatus::Continue;
     94    });
    10495}
    10596
     
    120111}
    121112
    122 static void trimDeadCellsFromList(HashSet<JSCell*>& knownLiveSet, CellList& list)
    123 {
    124     if (!list.hasLiveCells)
     113static void trimDeadCellsFromList(CellList& knownLiveSet, CellList& list)
     114{
     115    if (!list.size())
    125116        return;
    126117
    127     size_t liveCellsFound = 0;
    128     for (auto& cellProfile : list.liveCells) {
    129         if (cellProfile.isConfirmedDead)
     118    for (auto& cellProfile : list.cells()) {
     119        if (cellProfile.isDead())
    130120            continue; // Don't "resurrect" known dead cells.
    131         if (!knownLiveSet.contains(cellProfile.cell)) {
    132             cellProfile.isConfirmedDead = true;
     121        if (!knownLiveSet.find(cellProfile.cell())) {
     122            cellProfile.setIsDead();
    133123            continue;
    134124        }
    135         liveCellsFound++;
    136     }
    137     list.hasLiveCells = !!liveCellsFound;
     125        cellProfile.setIsLive();
     126    }
    138127}
    139128
    140129void HeapVerifier::trimDeadCells()
    141130{
    142     HashSet<JSCell*> knownLiveSet;
    143 
    144     CellList& after = currentCycle().after;
    145     for (auto& cellProfile : after.liveCells)
    146         knownLiveSet.add(cellProfile.cell);
     131    CellList& knownLiveSet = currentCycle().after;
    147132
    148133    trimDeadCellsFromList(knownLiveSet, currentCycle().before);
     
    154139}
    155140
    156 bool HeapVerifier::verifyButterflyIsInStorageSpace(Phase, CellList&)
    157 {
    158     // FIXME: Make this work again. https://bugs.webkit.org/show_bug.cgi?id=161752
     141void HeapVerifier::printVerificationHeader()
     142{
     143    RELEASE_ASSERT(m_heap->collectionScope());
     144    CollectionScope scope = currentCycle().scope;
     145    MonotonicTime gcCycleTimestamp = currentCycle().timestamp;
     146    dataLog("Verifying heap in [p", getCurrentProcessID(), ", t", currentThread(), "] vm ",
     147        RawPointer(m_heap->vm()), " on ", scope, " GC @ ", gcCycleTimestamp, "\n");
     148}
     149
     150bool HeapVerifier::verifyCellList(Phase phase, CellList& list)
     151{
     152    VM& vm = *m_heap->vm();
     153    auto& liveCells = list.cells();
     154
     155    bool listNamePrinted = false;
     156    auto printHeaderIfNeeded = [&] () {
     157        if (listNamePrinted)
     158            return;
     159       
     160        printVerificationHeader();
     161        dataLog(" @ phase ", phaseName(phase), ": FAILED in cell list '", list.name(), "' (size ", liveCells.size(), ")\n");
     162        listNamePrinted = true;
     163        m_didPrintLogs = true;
     164    };
     165   
     166    bool success = true;
     167    for (size_t i = 0; i < liveCells.size(); i++) {
     168        CellProfile& profile = liveCells[i];
     169        if (!profile.isLive())
     170            continue;
     171
     172        if (!profile.isJSCell())
     173            continue;
     174
     175        JSCell* cell = profile.jsCell();
     176        success |= validateJSCell(&vm, cell, &profile, &list, printHeaderIfNeeded, "  ");
     177    }
     178
     179    return success;
     180}
     181
     182bool HeapVerifier::validateCell(HeapCell* cell, VM* expectedVM)
     183{
     184    auto printNothing = [] () { };
     185
     186    if (cell->isZapped()) {
     187        dataLog("    cell ", RawPointer(cell), " is ZAPPED\n");
     188        return false;
     189    }
     190
     191    if (cell->cellKind() != HeapCell::JSCell)
     192        return true; // Nothing more to validate.
     193
     194    JSCell* jsCell = static_cast<JSCell*>(cell);
     195    return validateJSCell(expectedVM, jsCell, nullptr, nullptr, printNothing);
     196}
     197
     198bool HeapVerifier::validateJSCell(VM* expectedVM, JSCell* cell, CellProfile* profile, CellList* list, std::function<void()> printHeaderIfNeeded, const char* prefix)
     199{
     200    auto printHeaderAndCell = [cell, profile, printHeaderIfNeeded, prefix] () {
     201        printHeaderIfNeeded();
     202        dataLog(prefix, "cell ", RawPointer(cell));
     203        if (profile)
     204            dataLog(" [", profile->className(), "]");
     205    };
     206
     207    // 1. Validate the cell.
     208
     209    if (cell->isZapped()) {
     210        printHeaderAndCell();
     211        dataLog(" is zapped\n");
     212        return false;
     213    }
     214
     215    StructureID structureID = cell->structureID();
     216    if (!structureID) {
     217        printHeaderAndCell();
     218        dataLog(" has NULL structureID\n");
     219        return false;
     220    }
     221
     222    if (expectedVM) {
     223        VM& vm = *expectedVM;
     224
     225        VM* cellVM = cell->vm();
     226        if (cellVM != expectedVM) {
     227            printHeaderAndCell();
     228            dataLog(" is from a different VM: expected:", RawPointer(expectedVM), " actual:", RawPointer(cellVM), "\n");
     229            return false;
     230        }
     231
     232        // 2. Validate the cell's structure
     233
     234        Structure* structure = vm.getStructure(structureID);
     235        if (!structure) {
     236            printHeaderAndCell();
     237#if USE(JSVALUE64)
     238            uint32_t structureIDAsUint32 = structureID;
     239#else
     240            uint32_t structureIDAsUint32 = reinterpret_cast<uint32_t>(structureID);
     241#endif
     242            dataLog(" with structureID ", structureIDAsUint32, " maps to a NULL Structure pointer\n");
     243            return false;
     244        }
     245
     246        if (structure->isZapped()) {
     247            printHeaderAndCell();
     248            dataLog(" has ZAPPED structure ", RawPointer(structure), "\n");
     249            return false;
     250        }
     251
     252        if (!structure->structureID()) {
     253            printHeaderAndCell();
     254            dataLog(" has structure ", RawPointer(structure), " whose structureID is NULL\n");
     255            return false;
     256        }
     257
     258        VM* structureVM = structure->vm();
     259        if (structureVM != expectedVM) {
     260            printHeaderAndCell();
     261            dataLog(" has structure ", RawPointer(structure), " from a different VM: expected:", RawPointer(expectedVM), " actual:", RawPointer(structureVM), "\n");
     262            return false;
     263        }
     264
     265        if (list) {
     266            auto* structureProfile = list->find(structure);
     267            if (!structureProfile) {
     268                printHeaderAndCell();
     269                dataLog(" has structure ", RawPointer(structure), " NOT found in the live cell list\n");
     270                return false;
     271            }
     272
     273            if (!structureProfile->isLive()) {
     274                printHeaderAndCell();
     275                dataLog(" has DEAD structure ", RawPointer(structure), "\n");
     276                return false;
     277            }
     278        }
     279
     280        StructureID structureStructureID = structure->structureID();
     281        if (!structureStructureID) {
     282            printHeaderAndCell();
     283            dataLog(" has structure ", RawPointer(structure), " with a NULL structureID\n");
     284            return false;
     285        }
     286
     287        // 3. Validate the cell's structure's structure.
     288       
     289        Structure* structureStructure = vm.getStructure(structureID);
     290        if (!structureStructure) {
     291            printHeaderAndCell();
     292            dataLog(" has structure ", RawPointer(structure), " whose structure is NULL\n");
     293            return false;
     294        }
     295       
     296        if (structureStructure->isZapped()) {
     297            printHeaderAndCell();
     298            dataLog(" has structure ", RawPointer(structure), " whose structure ", RawPointer(structureStructure), " is ZAPPED\n");
     299            return false;
     300        }
     301       
     302        if (!structureStructure->structureID()) {
     303            printHeaderAndCell();
     304            dataLog(" has structure ", RawPointer(structure), " whose structure ", RawPointer(structureStructure), " has a NULL structureID\n");
     305            return false;
     306        }
     307       
     308        VM* structureStructureVM = structureStructure->vm();
     309        if (structureStructureVM != expectedVM) {
     310            printHeaderAndCell();
     311            dataLog(" has structure ", RawPointer(structure), " whose structure ", RawPointer(structureStructure), " is from a different VM: expected:", RawPointer(expectedVM), " actual:", RawPointer(structureStructureVM), "\n");
     312            return false;
     313        }
     314       
     315        if (list) {
     316            auto* structureStructureProfile = list->find(structureStructure);
     317            if (!structureStructureProfile) {
     318                printHeaderAndCell();
     319                dataLog(" has structure ", RawPointer(structure), " whose structure ", RawPointer(structureStructure), " is NOT found in the live cell list\n");
     320                return false;
     321            }
     322           
     323            if (!structureStructureProfile->isLive()) {
     324                printHeaderAndCell();
     325                dataLog(" has structure ", RawPointer(structure), " whose structure ", RawPointer(structureStructure), " is DEAD\n");
     326                return false;
     327            }
     328        }
     329       
     330        CodeBlock* codeBlock = jsDynamicCast<CodeBlock*>(vm, cell);
     331        if (UNLIKELY(codeBlock)) {
     332            bool success = true;
     333            for (unsigned i = 0; i < codeBlock->totalNumberOfValueProfiles(); ++i) {
     334                ValueProfile* valueProfile = codeBlock->getFromAllValueProfiles(i);
     335                for (unsigned i = 0; i < ValueProfile::totalNumberOfBuckets; ++i) {
     336                    JSValue value = JSValue::decode(valueProfile->m_buckets[i]);
     337                    if (!value)
     338                        continue;
     339                    if (!value.isCell())
     340                        continue;
     341                    JSCell* valueCell = value.asCell();
     342                    if (valueCell->isZapped()) {
     343                        printHeaderIfNeeded();
     344                        dataLog(prefix, "CodeBlock ", RawPointer(codeBlock), " has ZAPPED ValueProfile cell ", RawPointer(valueCell), "\n");
     345                        success = false;
     346                        continue;
     347                    }
     348                }
     349            }
     350            if (!success)
     351                return false;
     352        }
     353    }
     354
    159355    return true;
    160356}
     
    162358void HeapVerifier::verify(HeapVerifier::Phase phase)
    163359{
    164     bool beforeVerified = verifyButterflyIsInStorageSpace(phase, currentCycle().before);
    165     bool afterVerified = verifyButterflyIsInStorageSpace(phase, currentCycle().after);
    166     RELEASE_ASSERT(beforeVerified && afterVerified);
    167 }
    168 
    169 void HeapVerifier::reportCell(CellProfile& cellProfile, int cycleIndex, HeapVerifier::GCCycle& cycle, CellList& list)
    170 {
    171     JSCell* cell = cellProfile.cell;
    172 
    173     if (cellProfile.isConfirmedDead) {
    174         dataLogF("FOUND dead cell %p in GC[%d] %s list '%s'\n",
    175             cell, cycleIndex, collectionScopeName(cycle.scope), list.name);
    176         return;
    177     }
    178 
    179     if (cell->isObject()) {
    180         JSObject* object = static_cast<JSObject*>(cell);
    181         Structure* structure = object->structure();
    182         Butterfly* butterfly = object->butterfly();
    183         void* butterflyBase = butterfly->base(structure);
    184 
    185         dataLogF("FOUND object %p type '%s' butterfly %p (base %p) in GC[%d] %s list '%s'\n",
    186             object, structure->classInfo()->className,
    187             butterfly, butterflyBase,
    188             cycleIndex, collectionScopeName(cycle.scope), list.name);
    189     } else {
    190         Structure* structure = cell->structure();
    191         dataLogF("FOUND cell %p type '%s' in GC[%d] %s list '%s'\n",
    192             cell, structure->classInfo()->className,
    193             cycleIndex, collectionScopeName(cycle.scope), list.name);
    194     }
    195 }
    196 
    197 void HeapVerifier::checkIfRecorded(JSCell* cell)
     360    if (phase == Phase::AfterGC) {
     361        bool verified = verifyCellList(phase, currentCycle().after);
     362        RELEASE_ASSERT(verified);
     363    }
     364}
     365
     366void HeapVerifier::reportCell(CellProfile& profile, int cycleIndex, HeapVerifier::GCCycle& cycle, CellList& list, const char* prefix)
     367{
     368    HeapCell* cell = profile.cell();
     369    VM* vm = m_heap->vm();
     370
     371    if (prefix)
     372        dataLog(prefix);
     373
     374    dataLog("FOUND");
     375    if (profile.isLive())
     376        dataLog(" LIVE");
     377    else if (profile.isDead())
     378        dataLog(" DEAD");
     379
     380    if (!profile.isJSCell())
     381        dataLog(" HeapCell ");
     382    else
     383        dataLog(" JSCell ");
     384    dataLog(RawPointer(cell));
     385
     386    if (profile.className())
     387        dataLog(" [", profile.className(), "]");
     388
     389    if (profile.isLive() && profile.isJSCell()) {
     390        JSCell* jsCell = profile.jsCell();
     391        Structure* structure = jsCell->structure();
     392        dataLog(" structure:", RawPointer(structure));
     393        if (jsCell->isObject()) {
     394            JSObject* obj = static_cast<JSObject*>(cell);
     395            Butterfly* butterfly = obj->butterfly();
     396            void* butterflyBase = butterfly->base(structure);
     397           
     398            dataLog(" butterfly:", RawPointer(butterfly), " (base:", RawPointer(butterflyBase), ")");
     399        }
     400    }
     401
     402    dataLog(" in ", cycle.scope, " GC[", cycleIndex, "] in '", list.name(), "' list in VM ",
     403        RawPointer(vm), " recorded at time ", profile.timestamp(), "\n");
     404    if (profile.stackTrace())
     405        dataLog(*profile.stackTrace());
     406}
     407
     408void HeapVerifier::checkIfRecorded(HeapCell* cell)
    198409{
    199410    bool found = false;
     411    const char* const prefix = "  ";
     412    static const bool verbose = true;
    200413
    201414    for (int cycleIndex = 0; cycleIndex > -m_numberOfCycles; cycleIndex--) {
    202415        GCCycle& cycle = cycleForIndex(cycleIndex);
    203         CellList& beforeList = cycle.before;
    204         CellList& afterList = cycle.after;
    205 
    206         CellProfile* profile;
    207         profile = beforeList.findCell(cell);
    208         if (profile) {
    209             reportCell(*profile, cycleIndex, cycle, beforeList);
    210             found = true;
    211         }
    212         profile = afterList.findCell(cell);
    213         if (profile) {
    214             reportCell(*profile, cycleIndex, cycle, afterList);
    215             found = true;
     416        CellList* lists[] = { &cycle.before, &cycle.after };
     417
     418        if (verbose)
     419            dataLog("Checking ", cycle.scope, " GC<", cycle.timestamp, ">, cycle [", cycleIndex, "]:\n");
     420       
     421        const char* resultPrefix = "    ";
     422        for (auto* list : lists) {
     423            if (verbose)
     424                dataLog(prefix, "Cycle [", cycleIndex, "] '", list->name(), "' list: ");
     425
     426            CellProfile* profile = list->find(cell);
     427            if (profile) {
     428                reportCell(*profile, cycleIndex, cycle, *list, resultPrefix);
     429                found = true;
     430            } else if (verbose)
     431                dataLog(resultPrefix, "cell NOT found\n");
    216432        }
    217433    }
    218434
    219435    if (!found)
    220         dataLogF("cell %p NOT FOUND\n", cell);
     436        dataLog(prefix, "cell ", RawPointer(cell), " NOT FOUND\n");
     437}
     438
     439// The following are slower but more robust versions of the corresponding functions of the same name.
     440// These robust versions are designed so that we can call them interactively from a C++ debugger
     441// to query if a candidate is recorded cell.
     442
     443void HeapVerifier::checkIfRecorded(uintptr_t candidateCell)
     444{
     445    HeapCell* candidateHeapCell = reinterpret_cast<HeapCell*>(candidateCell);
     446   
     447    VMInspector& inspector = VMInspector::instance();
     448    auto expectedLocker = inspector.lock(Seconds(2));
     449    if (!expectedLocker) {
     450        ASSERT(expectedLocker.error() == VMInspector::Error::TimedOut);
     451        dataLog("ERROR: Timed out while waiting to iterate VMs.");
     452        return;
     453    }
     454
     455    auto& locker = expectedLocker.value();
     456    inspector.iterate(locker, [&] (VM& vm) {
     457        if (!vm.heap.m_verifier)
     458            return VMInspector::FunctorStatus::Continue;
     459       
     460        auto* verifier = vm.heap.m_verifier.get();
     461        dataLog("Search for cell ", RawPointer(candidateHeapCell), " in VM ", RawPointer(&vm), ":\n");
     462        verifier->checkIfRecorded(candidateHeapCell);
     463        return VMInspector::FunctorStatus::Continue;
     464    });
    221465}
    222466
  • trunk/Source/JavaScriptCore/tools/HeapVerifier.h

    r213675 r213883  
    11/*
    2  * Copyright (C) 2014-2015 Apple Inc. All rights reserved.
     2 * Copyright (C) 2014-2017 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    2828#include "CellList.h"
    2929#include "Heap.h"
     30#include <wtf/MonotonicTime.h>
    3031
    3132namespace JSC {
     
    4647    HeapVerifier(Heap*, unsigned numberOfGCCyclesToRecord);
    4748
    48     void initializeGCCycle();
     49    void startGC();
     50    void endGC();
     51
    4952    void gatherLiveCells(Phase);
    5053    void trimDeadCells();
    5154    void verify(Phase);
    5255
     56    static const char* phaseName(Phase);
     57   
    5358    // Scans all previously recorded CellLists and checks if the specified
    5459    // cell was in any of those lists.
    55     JS_EXPORT_PRIVATE void checkIfRecorded(JSCell*);
     60    JS_EXPORT_PRIVATE static void checkIfRecorded(uintptr_t maybeCell);
    5661
    57     static const char* phaseName(Phase);
     62    // Returns false if anything is found to be inconsistent/incorrect about the specified cell.
     63    JS_EXPORT_PRIVATE static bool validateCell(HeapCell*, VM* expectedVM = nullptr);
    5864
    5965private:
     
    6571        }
    6672
     73        void reset()
     74        {
     75            before.reset();
     76            after.reset();
     77        }
     78
    6779        CollectionScope scope;
     80        MonotonicTime timestamp;
    6881        CellList before;
    6982        CellList after;
     
    8396
    8497    CellList* cellListForGathering(Phase);
    85     bool verifyButterflyIsInStorageSpace(Phase, CellList&);
     98    bool verifyCellList(Phase, CellList&);
     99    static bool validateJSCell(VM* expectedVM, JSCell*, CellProfile*, CellList*, std::function<void()> printHeaderIfNeeded, const char* prefix = "");
    86100
    87     static void reportCell(CellProfile&, int cycleIndex, HeapVerifier::GCCycle&, CellList&);
     101    void printVerificationHeader();
     102
     103    void checkIfRecorded(HeapCell* maybeHeapCell);
     104    void reportCell(CellProfile&, int cycleIndex, HeapVerifier::GCCycle&, CellList&, const char* prefix = nullptr);
    88105
    89106    Heap* m_heap;
    90107    int m_currentCycle;
    91108    int m_numberOfCycles;
     109    bool m_didPrintLogs { false };
    92110    std::unique_ptr<GCCycle[]> m_cycles;
    93111};
Note: See TracChangeset for help on using the changeset viewer.