/*
 * Copyright (C) 2013-2021 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. AND ITS CONTRIBUTORS ``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 ITS 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 "StructureRareData.h"

#include "AdaptiveInferredPropertyValueWatchpointBase.h"
#include "CacheableIdentifierInlines.h"
#include "CachedSpecialPropertyAdaptiveStructureWatchpoint.h"
#include "JSCellButterfly.h"
#include "JSObjectInlines.h"
#include "JSPropertyNameEnumerator.h"
#include "JSString.h"
#include "ObjectPropertyConditionSet.h"
#include "StructureChain.h"
#include "StructureInlines.h"
#include "StructureRareDataInlines.h"
#include <wtf/TZoneMalloc.h>
#include <wtf/TZoneMallocInlines.h>

WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN

namespace JSC {

const ClassInfo StructureRareData::s_info = { "StructureRareData"_s, nullptr, nullptr, nullptr, CREATE_METHOD_TABLE(StructureRareData) };

Structure* StructureRareData::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
{
    return Structure::create(vm, globalObject, prototype, TypeInfo(CellType, StructureFlags), info());
}

StructureRareData* StructureRareData::create(VM& vm, Structure* previous)
{
    StructureRareData* rareData = new (NotNull, allocateCell<StructureRareData>(vm)) StructureRareData(vm, previous);
    rareData->finishCreation(vm);
    return rareData;
}

void StructureRareData::destroy(JSCell* cell)
{
    static_cast<StructureRareData*>(cell)->StructureRareData::~StructureRareData();
}

StructureRareData::StructureRareData(VM& vm, Structure* previous)
    : JSCell(vm, vm.structureRareDataStructure.get())
    , m_previous(previous, WriteBarrierEarlyInit)
    , m_maxOffset(invalidOffset)
    , m_transitionOffset(invalidOffset)
{
}

template<typename Visitor>
void StructureRareData::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
    StructureRareData* thisObject = jsCast<StructureRareData*>(cell);
    ASSERT_GC_OBJECT_INHERITS(thisObject, info());

    Base::visitChildren(thisObject, visitor);
    visitor.append(thisObject->m_previous);
    if (thisObject->m_specialPropertyCache) {
        for (unsigned index = 0; index < numberOfCachedSpecialPropertyKeys; ++index)
            visitor.appendUnbarriered(thisObject->cachedSpecialProperty(static_cast<CachedSpecialPropertyKey>(index)));
    }
    visitor.appendUnbarriered(thisObject->cachedPropertyNameEnumerator());
    for (unsigned index = 0; index < numberOfCachedPropertyNames; ++index) {
        auto* cached = thisObject->m_cachedPropertyNames[index].unvalidatedGet();
        if (cached != cachedPropertyNamesSentinel())
            visitor.appendUnbarriered(cached);
    }
}

DEFINE_VISIT_CHILDREN(StructureRareData);

// ----------- Cached special properties helper watchpoint classes -----------

class CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint final : public AdaptiveInferredPropertyValueWatchpointBase {
    WTF_MAKE_TZONE_ALLOCATED(CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint);
public:
    typedef AdaptiveInferredPropertyValueWatchpointBase Base;
    CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint(const ObjectPropertyCondition&, StructureRareData*);

private:
    bool isValid() const final;
    void handleFire(VM&, const FireDetail&) final;

    StructureRareData* m_structureRareData;
};

SpecialPropertyCacheEntry::~SpecialPropertyCacheEntry() = default;

SpecialPropertyCache& StructureRareData::ensureSpecialPropertyCacheSlow()
{
    ASSERT(!isCompilationThread() && !Thread::mayBeGCThread());
    ASSERT(!m_specialPropertyCache);
    auto cache = makeUnique<SpecialPropertyCache>();
    WTF::storeStoreFence(); // Expose valid struct for concurrent threads including concurrent compilers.
    m_specialPropertyCache = WTF::move(cache);
    return *m_specialPropertyCache.get();
}

inline void StructureRareData::giveUpOnSpecialPropertyCache(CachedSpecialPropertyKey key)
{
    ensureSpecialPropertyCache().m_cache[static_cast<unsigned>(key)].m_value.setWithoutWriteBarrier(JSCell::seenMultipleCalleeObjects());
}

void StructureRareData::cacheSpecialPropertySlow(JSGlobalObject* globalObject, VM& vm, Structure* ownStructure, JSValue value, CachedSpecialPropertyKey key, const PropertySlot& slot)
{
    UniquedStringImpl* uid = nullptr;
    switch (key) {
    case CachedSpecialPropertyKey::ToStringTag:
        uid = vm.propertyNames->toStringTagSymbol.impl();
        break;
    case CachedSpecialPropertyKey::ToString:
        uid = vm.propertyNames->toString.impl();
        break;
    case CachedSpecialPropertyKey::ValueOf:
        uid = vm.propertyNames->valueOf.impl();
        break;
    case CachedSpecialPropertyKey::ToPrimitive:
        uid = vm.propertyNames->toPrimitiveSymbol.impl();
        break;
    case CachedSpecialPropertyKey::ToJSON:
        uid = vm.propertyNames->toJSON.impl();
        break;
    }

    if (!ownStructure->propertyAccessesAreCacheable() || ownStructure->isProxy()) {
        giveUpOnSpecialPropertyCache(key);
        return;
    }

    ObjectPropertyConditionSet conditionSet;
    if (slot.isValue()) {
        // We don't handle the own property case of special properties (toString, valueOf, @@toPrimitive, @@toStringTag) because we would never know if a new
        // object transitioning to the same structure had the same value stored in that property.
        // Additionally, this is a super unlikely case anyway.
        if (!slot.isCacheable() || slot.slotBase()->structure() == ownStructure)
            return;

        // This will not create a condition for the current structure but that is good because we know that property
        // is not on the ownStructure so we will transisition if one is added and this cache will no longer be used.
        auto cacheStatus = prepareChainForCaching(globalObject, ownStructure, uid, slot.slotBase());
        if (!cacheStatus) {
            giveUpOnSpecialPropertyCache(key);
            return;
        }
        conditionSet = generateConditionsForPrototypePropertyHit(vm, this, globalObject, ownStructure, slot.slotBase(), uid);
        ASSERT(!conditionSet.isValid() || conditionSet.hasOneSlotBaseCondition());
    } else if (slot.isUnset()) {
        if (!ownStructure->propertyAccessesAreCacheableForAbsence()) {
            giveUpOnSpecialPropertyCache(key);
            return;
        }

        auto cacheStatus = prepareChainForCaching(globalObject, ownStructure, uid, nullptr);
        if (!cacheStatus) {
            giveUpOnSpecialPropertyCache(key);
            return;
        }
        conditionSet = generateConditionsForPropertyMiss(vm, this, globalObject, ownStructure, uid);
    } else
        return;

    if (!conditionSet.isValid()) {
        giveUpOnSpecialPropertyCache(key);
        return;
    }

    ObjectPropertyCondition equivCondition;
    for (const ObjectPropertyCondition& condition : conditionSet) {
        if (condition.condition().kind() == PropertyCondition::Presence) {
            ASSERT(isValidOffset(condition.offset()));
            condition.object()->structure()->startWatchingPropertyForReplacements(vm, condition.offset());
            equivCondition = condition.attemptToMakeEquivalenceWithoutBarrier();

            // The equivalence condition won't be watchable if we have already seen a replacement.
            if (!equivCondition.isWatchable(PropertyCondition::MakeNoChanges)) {
                giveUpOnSpecialPropertyCache(key);
                return;
            }
        } else if (!condition.isWatchable(PropertyCondition::MakeNoChanges)) {
            giveUpOnSpecialPropertyCache(key);
            return;
        }
    }

    ASSERT(conditionSet.structuresEnsureValidity());
    auto& cache = ensureSpecialPropertyCache().m_cache[static_cast<unsigned>(key)];
    for (ObjectPropertyCondition condition : conditionSet) {
        if (condition.condition().kind() == PropertyCondition::Presence) {
            cache.m_equivalenceWatchpoint = makeUnique<CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint>(equivCondition, this);
            cache.m_equivalenceWatchpoint->install(vm);
        } else
            cache.m_missWatchpoints.add(condition, this)->install(vm);
    }
    cache.m_value.set(vm, this, value);
}

void StructureRareData::clearCachedSpecialProperty(CachedSpecialPropertyKey key)
{
    auto* objectToStringCache = m_specialPropertyCache.get();
    if (!objectToStringCache)
        return;
    auto& cache = objectToStringCache->m_cache[static_cast<unsigned>(key)];
    cache.m_missWatchpoints.clear();
    cache.m_equivalenceWatchpoint.reset();
    if (cache.m_value.get() != JSCell::seenMultipleCalleeObjects())
        cache.m_value.clear();
}

void StructureRareData::finalizeUnconditionally(VM& vm, CollectionScope)
{
    if (m_specialPropertyCache) {
        auto clearCacheIfInvalidated = [&](CachedSpecialPropertyKey key) {
            auto& cache = m_specialPropertyCache->m_cache[static_cast<unsigned>(key)];
            if (cache.m_equivalenceWatchpoint) {
                if (!cache.m_equivalenceWatchpoint->key().isStillLive(vm)) {
                    clearCachedSpecialProperty(key);
                    return;
                }
            }
            for (auto* watchpoint : cache.m_missWatchpoints) {
                if (!watchpoint->key().isStillLive(vm)) {
                    clearCachedSpecialProperty(key);
                    return;
                }
            }
        };

        for (unsigned index = 0; index < numberOfCachedSpecialPropertyKeys; ++index)
            clearCacheIfInvalidated(static_cast<CachedSpecialPropertyKey>(index));
    }
}

// ------------- Methods for Object.prototype.toString() helper watchpoint classes --------------

WTF_MAKE_TZONE_ALLOCATED_IMPL(CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint);

CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint::CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint(const ObjectPropertyCondition& key, StructureRareData* structureRareData)
    : Base(key)
    , m_structureRareData(structureRareData)
{
}

bool CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint::isValid() const
{
    return !m_structureRareData->isPendingDestruction();
}

void CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint::handleFire(VM& vm, const FireDetail&)
{
    CachedSpecialPropertyKey key = CachedSpecialPropertyKey::ToStringTag;
    if (this->key().uid() == vm.propertyNames->toStringTagSymbol.impl())
        key = CachedSpecialPropertyKey::ToStringTag;
    else if (this->key().uid() == vm.propertyNames->toString.impl())
        key = CachedSpecialPropertyKey::ToString;
    else if (this->key().uid() == vm.propertyNames->valueOf.impl())
        key = CachedSpecialPropertyKey::ValueOf;
    else if (this->key().uid() == vm.propertyNames->toJSON.impl())
        key = CachedSpecialPropertyKey::ToJSON;
    else {
        ASSERT(this->key().uid() == vm.propertyNames->toPrimitiveSymbol.impl());
        key = CachedSpecialPropertyKey::ToPrimitive;
    }
    m_structureRareData->clearCachedSpecialProperty(key);
}

} // namespace JSC

WTF_ALLOW_UNSAFE_BUFFER_USAGE_END
