/*
 * 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 &currentCycle().before;
    case Phase::AfterMarking:
        return &currentCycle().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 const 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
