/*
 *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
 *  Copyright (C) 2003-2022 Apple Inc. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#pragma once

#include "BatchedTransitionOptimizer.h"
#include "CallFrame.h"
#include "CustomGetterSetter.h"
#include "DOMJITGetterSetter.h"
#include "DOMJITSignature.h"
#include "Identifier.h"
#include "IdentifierInlines.h"
#include "ImplementationVisibility.h"
#include "Intrinsic.h"
#include "JSFunction.h"
#include "JSGlobalObject.h"
#include "LazyProperty.h"
#include "PropertySlot.h"
#include "PutPropertySlot.h"
#include "TypeError.h"
#include <wtf/Assertions.h>

namespace JSC {

struct CompactHashIndex {
    const int16_t value;
    const int16_t next;
};

typedef FunctionExecutable* (*BuiltinGenerator)(VM&);
typedef JSValue (*LazyPropertyCallback)(VM&, JSObject*);

// Hash table generated by the create_hash_table script.
struct HashTableValue {

#define FOR_EACH_HASH_ATBLE_VALUE_TYPE(v) \
    v(Accessor) \
    v(BuiltinAccessor) \
    v(BuiltinGenerator) \
    v(Constant) \
    v(DOMJITAttribute) \
    v(DOMJITFunction) \
    v(GetterSetter) \
    v(LazyCellProperty) \
    v(LazyClassStructure) \
    v(LazyProperty) \
    v(Lexer) \
    v(NativeFunction) \

#if ASSERT_ENABLED
    enum class ValueType {
#define DECLARE_TYPE(name) name,
        FOR_EACH_HASH_ATBLE_VALUE_TYPE(DECLARE_TYPE)
        End,
#undef DECLARE_TYPE
    };

#define DECLARE_TYPE() ValueType valueType;
#define INIT_TYPE(x) ValueType::x
#else
#define DECLARE_TYPE()
#define INIT_TYPE(x)
#endif // ASSERT_ENABLED

#define DECLARE_TAG(name) enum name##TypeTag { name##Type };
    FOR_EACH_HASH_ATBLE_VALUE_TYPE(DECLARE_TAG)
    enum EndTag { End };
#undef DECLARE_TAG

    ASCIILiteral m_key; // property name
    unsigned m_attributes; // JSObject attributes
    Intrinsic m_intrinsic;
    union ValueStorage {
        // PropertyAttribute::Accessor
        constexpr ValueStorage(AccessorTypeTag, NativeFunction::Ptr getter, NativeFunction::Ptr setter)
            : accessor({ getter, setter, INIT_TYPE(Accessor) })
        { }
        // PropertyAttribute::Builtin | PropertyAttribute::Accessor
        constexpr ValueStorage(BuiltinAccessorTypeTag, BuiltinGenerator getter, BuiltinGenerator setter)
            : builtinAccessor({ getter, setter, INIT_TYPE(BuiltinAccessor) })
        { }
        // PropertyAttribute::Builtin
        constexpr ValueStorage(BuiltinGeneratorTypeTag, BuiltinGenerator generator, intptr_t argCount)
            : builtinGenerator({ generator, argCount, INIT_TYPE(BuiltinGenerator) })
        { }
        // PropertyAttribute::ConstantInteger
        constexpr ValueStorage(ConstantTypeTag, long long constant)
            : constant({ constant, 0, INIT_TYPE(Constant) })
        { }
        // PropertyAttribute::DOMJITAttribute
        constexpr ValueStorage(DOMJITAttributeTypeTag, const DOMJIT::GetterSetter* getterSetter, PutValueFunc::Ptr setter)
            : domJITAttribute({ getterSetter, setter, INIT_TYPE(DOMJITAttribute) })
        { }
        // PropertyAttribute::Function | PropertyAttribute::DOMJITFunction
        constexpr ValueStorage(DOMJITFunctionTypeTag, NativeFunction::Ptr function, const DOMJIT::Signature* signature)
            : domJITFunction({ function, signature, INIT_TYPE(DOMJITFunction) })
        { }
        // PropertyAttribute::DOMAttribute
        constexpr ValueStorage(GetterSetterTypeTag, GetValueFunc::Ptr getter, PutValueFunc::Ptr setter)
            : getterSetter({ getter, setter, INIT_TYPE(GetterSetter) })
        { }
        constexpr ValueStorage(LazyCellPropertyTypeTag, ptrdiff_t offset)
            : lazyCellProperty({ offset, 0, INIT_TYPE(LazyCellProperty) })
        { }
        constexpr ValueStorage(LazyClassStructureTypeTag, ptrdiff_t offset)
            : lazyClassStructure({ offset, 0, INIT_TYPE(LazyClassStructure) })
        { }
        constexpr ValueStorage(LazyPropertyTypeTag, LazyPropertyCallback callback)
            : lazyProperty({ callback, 0, INIT_TYPE(LazyProperty) })
        { }
        constexpr ValueStorage(LexerTypeTag, intptr_t value)
            : lexer({ value, 0, INIT_TYPE(Lexer) })
        { }
        // PropertyAttribute::Function
        constexpr ValueStorage(NativeFunctionTypeTag, NativeFunction::Ptr function, intptr_t argCount)
            : nativeFunction({ function, argCount, INIT_TYPE(NativeFunction) })
        { }
        constexpr ValueStorage(EndTag)
            : bits({ 0, 0, INIT_TYPE(End) })
        { }

#define HASH_TABLE_VALUE_ATTR(discriminator) WTF_VTBL_FUNCPTR_PTRAUTH_STR("HashTableValue" #discriminator)

        struct {
            HASH_TABLE_VALUE_ATTR(Accessor) NativeFunction::Ptr getter;
            HASH_TABLE_VALUE_ATTR(Accessor) NativeFunction::Ptr setter;
            DECLARE_TYPE()
        } accessor;
        struct {
            HASH_TABLE_VALUE_ATTR(BuiltinAccessor) BuiltinGenerator getter;
            HASH_TABLE_VALUE_ATTR(BuiltinAccessor) BuiltinGenerator setter;
            DECLARE_TYPE()
        } builtinAccessor;
        struct {
            HASH_TABLE_VALUE_ATTR(BuiltinGenerator) BuiltinGenerator generator;
            intptr_t argCount;
            DECLARE_TYPE()
        } builtinGenerator;
        struct {
            long long value;
            intptr_t unused;
            DECLARE_TYPE()
        } constant;
        struct {
            const DOMJIT::GetterSetter* getterSetter;
            HASH_TABLE_VALUE_ATTR(DOMJITAttribute) PutValueFunc::Ptr setter;
            DECLARE_TYPE()
        } domJITAttribute;
        struct {
            HASH_TABLE_VALUE_ATTR(DOMJITFunction) NativeFunction::Ptr function;
            const DOMJIT::Signature* signature;
            DECLARE_TYPE()
        } domJITFunction;
        struct {
            HASH_TABLE_VALUE_ATTR(GetterSetter) GetValueFunc::Ptr getter;
            HASH_TABLE_VALUE_ATTR(GetterSetter) PutValueFunc::Ptr setter;
            DECLARE_TYPE()
        } getterSetter;
        struct {
            ptrdiff_t offset;
            intptr_t unused;
            DECLARE_TYPE()
        } lazyCellProperty;
        struct {
            ptrdiff_t offset;
            intptr_t unused;
            DECLARE_TYPE()
        } lazyClassStructure;
        struct {
            HASH_TABLE_VALUE_ATTR(LazyPropertyCallback) LazyPropertyCallback callback;
            intptr_t unused;
            DECLARE_TYPE()
        } lazyProperty;
        struct {
            intptr_t value;
            intptr_t unused;
            DECLARE_TYPE()
        } lexer;
        struct {
            HASH_TABLE_VALUE_ATTR(NativeFunction) NativeFunction::Ptr function;
            intptr_t argumentCount;
            DECLARE_TYPE()
        } nativeFunction;

        // Only used for null testing (e.g. hasGetter) and termination (e.g. the "End" entry).
        struct {
            intptr_t value1;
            intptr_t value2;
            DECLARE_TYPE()
        } bits;
    } m_values;

    unsigned attributes() const { return m_attributes; }

    Intrinsic intrinsic() const { ASSERT(m_attributes & PropertyAttribute::Function); return m_intrinsic; }
    BuiltinGenerator builtinGenerator() const
    {
        ASSERT(m_attributes & PropertyAttribute::Builtin);
        ASSERT(m_values.builtinGenerator.valueType == ValueType::BuiltinGenerator);
        return m_values.builtinGenerator.generator;
    }
    NativeFunction function() const
    {
        ASSERT(m_attributes & PropertyAttribute::Function);
        ASSERT(m_values.nativeFunction.valueType == ValueType::NativeFunction);
        return NativeFunction(m_values.nativeFunction.function);
    }
    unsigned char functionLength() const
    {
        ASSERT(m_attributes & PropertyAttribute::Function);
        if (m_attributes & PropertyAttribute::DOMJITFunction) {
            ASSERT(m_values.domJITFunction.valueType == ValueType::DOMJITFunction);
            return signature()->argumentCount;
        }
        ASSERT(m_values.nativeFunction.valueType == ValueType::NativeFunction);
        return static_cast<unsigned char>(m_values.nativeFunction.argumentCount);
    }

    bool hasGetter() const { return m_values.bits.value1; }
    bool hasSetter() const { return m_values.bits.value2; }

    GetValueFunc propertyGetter() const
    {
        ASSERT(!(m_attributes & PropertyAttribute::BuiltinOrFunctionOrAccessorOrLazyPropertyOrConstant));
        ASSERT(m_values.getterSetter.valueType == ValueType::GetterSetter);
        return GetValueFunc(m_values.getterSetter.getter);
    }
    PutValueFunc propertyPutter() const
    {
        ASSERT(!(m_attributes & PropertyAttribute::BuiltinOrFunctionOrAccessorOrLazyPropertyOrConstant));
        ASSERT(m_values.getterSetter.valueType == ValueType::GetterSetter);
        return PutValueFunc(m_values.getterSetter.setter);
    }

    NativeFunction domJITFunction() const
    {
        ASSERT(m_attributes & PropertyAttribute::Function);
        ASSERT(m_values.nativeFunction.valueType == ValueType::DOMJITFunction);
        return NativeFunction(m_values.domJITFunction.function);
    }

    const DOMJIT::GetterSetter* domJIT() const
    {
        ASSERT(m_attributes & PropertyAttribute::DOMJITAttribute);
        ASSERT(m_values.domJITAttribute.valueType == ValueType::DOMJITAttribute);
        return m_values.domJITAttribute.getterSetter;
    }
    PutValueFunc domJITSetter() const
    {
        ASSERT(m_attributes & PropertyAttribute::DOMJITAttribute);
        ASSERT(m_values.domJITAttribute.valueType == ValueType::DOMJITAttribute);
        return m_values.domJITAttribute.setter;
    }
    const DOMJIT::Signature* signature() const
    {
        ASSERT(m_attributes & PropertyAttribute::DOMJITFunction);
        ASSERT(m_values.domJITFunction.valueType == ValueType::DOMJITFunction);
        return m_values.domJITFunction.signature;
    }

    NativeFunction accessorGetter() const
    {
        ASSERT(m_attributes & PropertyAttribute::Accessor);
        ASSERT(m_values.accessor.valueType == ValueType::Accessor);
        return NativeFunction(m_values.accessor.getter);
    }
    NativeFunction accessorSetter() const
    {
        ASSERT(m_attributes & PropertyAttribute::Accessor);
        ASSERT(m_values.accessor.valueType == ValueType::Accessor);
        return NativeFunction(m_values.accessor.setter);
    }
    BuiltinGenerator builtinAccessorGetterGenerator() const;
    BuiltinGenerator builtinAccessorSetterGenerator() const;

    long long constantInteger() const
    {
        ASSERT(m_attributes & PropertyAttribute::ConstantInteger);
        ASSERT(m_values.constant.valueType == ValueType::Constant);
        return m_values.constant.value;
    }

    intptr_t lexerValue() const
    {
        ASSERT(!m_attributes);
        ASSERT(m_values.lexer.valueType == ValueType::Lexer);
        return m_values.lexer.value;
    }
    
    ptrdiff_t lazyCellPropertyOffset() const
    {
        ASSERT(m_attributes & PropertyAttribute::CellProperty);
        ASSERT(m_values.lazyCellProperty.valueType == ValueType::LazyCellProperty);
        return m_values.lazyCellProperty.offset;
    }
    ptrdiff_t lazyClassStructureOffset() const
    {
        ASSERT(m_attributes & PropertyAttribute::ClassStructure);
        ASSERT(m_values.lazyClassStructure.valueType == ValueType::LazyClassStructure);
        return m_values.lazyClassStructure.offset;
    }
    LazyPropertyCallback lazyPropertyCallback() const
    {
        ASSERT(m_attributes & PropertyAttribute::PropertyCallback);
        ASSERT(m_values.lazyProperty.valueType == ValueType::LazyProperty);
        return m_values.lazyProperty.callback;
    }
};

struct HashTable {
    int numberOfValues;
    int indexMask;
    bool hasSetterOrReadonlyProperties;
    const ClassInfo* classForThis; // Used by DOMAttribute. Attribute accessors perform type check against this classInfo.

    const HashTableValue* values; // Fixed values generated by script.
    const CompactHashIndex* index;

    // Find an entry in the table, and return the entry.
    ALWAYS_INLINE const HashTableValue* entry(PropertyName propertyName) const
    {
        if (propertyName.isSymbol())
            return nullptr;

        auto uid = propertyName.uid();
        if (!uid)
            return nullptr;

        int indexEntry = IdentifierRepHash::hash(uid) & indexMask;
        int valueIndex = index[indexEntry].value;
        if (valueIndex == -1)
            return nullptr;

        while (true) {
            if (!values[valueIndex].m_key.isNull() && WTF::equal(uid, values[valueIndex].m_key))
                return &values[valueIndex];

            indexEntry = index[indexEntry].next;
            if (indexEntry == -1)
                return nullptr;
            valueIndex = index[indexEntry].value;
            ASSERT(valueIndex != -1);
        };
    }

    class ConstIterator {
    public:
        ConstIterator(const HashTable* table, int position)
            : m_table(table)
            , m_position(position)
        {
            skipInvalidKeys();
        }

        const HashTableValue* value() const
        {
            return &m_table->values[m_position];
        }

        const HashTableValue& operator*() const { return *value(); }

        ASCIILiteral key() const
        {
            return m_table->values[m_position].m_key;
        }

        const HashTableValue* operator->() const
        {
            return value();
        }

        bool operator!=(const ConstIterator& other) const
        {
            ASSERT(m_table == other.m_table);
            return m_position != other.m_position;
        }

        ConstIterator& operator++()
        {
            ASSERT(m_position < m_table->numberOfValues);
            ++m_position;
            skipInvalidKeys();
            return *this;
        }

    private:
        void skipInvalidKeys()
        {
            ASSERT(m_position <= m_table->numberOfValues);
            while (m_position < m_table->numberOfValues && m_table->values[m_position].m_key.isNull())
                ++m_position;
            ASSERT(m_position <= m_table->numberOfValues);
        }

        const HashTable* m_table;
        int m_position;
    };

    ConstIterator begin() const
    {
        return ConstIterator(this, 0);
    }
    ConstIterator end() const
    {
        return ConstIterator(this, numberOfValues);
    }
};

JS_EXPORT_PRIVATE bool setUpStaticFunctionSlot(VM&, const ClassInfo*, const HashTableValue*, JSObject* thisObject, PropertyName, PropertySlot&);
JS_EXPORT_PRIVATE void reifyStaticAccessor(VM&, const HashTableValue&, JSObject& thisObject, PropertyName);

inline BuiltinGenerator HashTableValue::builtinAccessorGetterGenerator() const
{
    ASSERT(m_attributes & PropertyAttribute::Accessor);
    ASSERT(m_attributes & PropertyAttribute::Builtin);
    ASSERT(m_values.builtinAccessor.valueType == ValueType::BuiltinAccessor);
    return reinterpret_cast<BuiltinGenerator>(m_values.builtinAccessor.getter);
}

inline BuiltinGenerator HashTableValue::builtinAccessorSetterGenerator() const
{
    ASSERT(m_attributes & PropertyAttribute::Accessor);
    ASSERT(m_attributes & PropertyAttribute::Builtin);
    ASSERT(m_values.builtinAccessor.valueType == ValueType::BuiltinAccessor);
    return reinterpret_cast<BuiltinGenerator>(m_values.builtinAccessor.setter);
}

inline bool getStaticPropertySlotFromTable(VM& vm, const ClassInfo* classInfo, const HashTable& table, JSObject* thisObject, PropertyName propertyName, PropertySlot& slot)
{
    if (thisObject->staticPropertiesReified())
        return false;

    auto* entry = table.entry(propertyName);
    if (!entry)
        return false;

    if (entry->attributes() & PropertyAttribute::BuiltinOrFunctionOrAccessorOrLazyProperty)
        return setUpStaticFunctionSlot(vm, classInfo, entry, thisObject, propertyName, slot);

    if (entry->attributes() & PropertyAttribute::ConstantInteger) {
        slot.setValue(thisObject, attributesForStructure(entry->attributes()), jsNumber(entry->constantInteger()));
        return true;
    }

    if (entry->attributes() & PropertyAttribute::DOMJITAttribute) {
        ASSERT_WITH_MESSAGE(entry->attributes() & PropertyAttribute::ReadOnly, "DOMJITAttribute supports readonly attributes currently.");
        const DOMJIT::GetterSetter* domJIT = entry->domJIT();
        slot.setCacheableCustom(thisObject, attributesForStructure(entry->attributes()), domJIT->getter(), entry->domJITSetter(), DOMAttributeAnnotation { classInfo, domJIT });
        return true;
    }

    if (entry->attributes() & PropertyAttribute::DOMAttribute) {
        slot.setCacheableCustom(thisObject, attributesForStructure(entry->attributes()), entry->propertyGetter(), entry->propertyPutter(), DOMAttributeAnnotation { classInfo, nullptr });
        return true;
    }

    slot.setCacheableCustom(thisObject, attributesForStructure(entry->attributes()), entry->propertyGetter(), entry->propertyPutter());
    return true;
}

inline void reifyStaticProperty(VM& vm, const ClassInfo* classInfo, const PropertyName& propertyName, const HashTableValue& value, JSObject& thisObj)
{
    if (value.attributes() & PropertyAttribute::Builtin) {
        if (value.attributes() & PropertyAttribute::Accessor)
            reifyStaticAccessor(vm, value, thisObj, propertyName);
        else
            thisObj.putDirectBuiltinFunction(vm, thisObj.globalObject(), propertyName, value.builtinGenerator()(vm), attributesForStructure(value.attributes()));
        return;
    }

    if (value.attributes() & PropertyAttribute::Function) {
        if (value.attributes() & PropertyAttribute::DOMJITFunction) {
            thisObj.putDirectNativeFunction(
                vm, thisObj.globalObject(), propertyName, value.functionLength(),
                value.domJITFunction(), ImplementationVisibility::Public, value.intrinsic(), value.signature(), attributesForStructure(value.attributes()));
            return;
        }
        thisObj.putDirectNativeFunction(
            vm, thisObj.globalObject(), propertyName, value.functionLength(),
            value.function(), ImplementationVisibility::Public, value.intrinsic(), attributesForStructure(value.attributes()));
        return;
    }

    if (value.attributes() & PropertyAttribute::ConstantInteger) {
        thisObj.putDirect(vm, propertyName, jsNumber(value.constantInteger()), attributesForStructure(value.attributes()));
        return;
    }

    if (value.attributes() & PropertyAttribute::Accessor) {
        reifyStaticAccessor(vm, value, thisObj, propertyName);
        return;
    }
    
    if (value.attributes() & PropertyAttribute::CellProperty) {
        LazyCellProperty* property = bitwise_cast<LazyCellProperty*>(
            bitwise_cast<char*>(&thisObj) + value.lazyCellPropertyOffset());
        JSCell* result = property->get(&thisObj);
        thisObj.putDirect(vm, propertyName, result, attributesForStructure(value.attributes()));
        return;
    }
    
    if (value.attributes() & PropertyAttribute::ClassStructure) {
        LazyClassStructure* lazyStructure = bitwise_cast<LazyClassStructure*>(
            bitwise_cast<char*>(&thisObj) + value.lazyClassStructureOffset());
        JSObject* constructor = lazyStructure->constructor(jsCast<JSGlobalObject*>(&thisObj));
        thisObj.putDirect(vm, propertyName, constructor, attributesForStructure(value.attributes()));
        return;
    }
    
    if (value.attributes() & PropertyAttribute::PropertyCallback) {
        JSValue result = value.lazyPropertyCallback()(vm, &thisObj);
        thisObj.putDirect(vm, propertyName, result, attributesForStructure(value.attributes()));
        return;
    }

    if (value.attributes() & PropertyAttribute::DOMJITAttribute) {
        ASSERT_WITH_MESSAGE(classInfo, "DOMJITAttribute should have class info for type checking.");
        const DOMJIT::GetterSetter* domJIT = value.domJIT();
        auto* customGetterSetter = DOMAttributeGetterSetter::create(vm, domJIT->getter(), value.domJITSetter(), DOMAttributeAnnotation { classInfo, domJIT });
        thisObj.putDirectCustomAccessor(vm, propertyName, customGetterSetter, attributesForStructure(value.attributes()));
        return;
    }

    if (value.attributes() & PropertyAttribute::DOMAttribute) {
        ASSERT_WITH_MESSAGE(classInfo, "DOMAttribute should have class info for type checking.");
        auto* customGetterSetter = DOMAttributeGetterSetter::create(vm, value.propertyGetter(), value.propertyPutter(), DOMAttributeAnnotation { classInfo, nullptr });
        thisObj.putDirectCustomAccessor(vm, propertyName, customGetterSetter, attributesForStructure(value.attributes()));
        return;
    }

    CustomGetterSetter* customGetterSetter = CustomGetterSetter::create(vm, value.propertyGetter(), value.propertyPutter());
    thisObj.putDirectCustomAccessor(vm, propertyName, customGetterSetter, attributesForStructure(value.attributes()));
}

template<unsigned numberOfValues>
inline void reifyStaticProperties(VM& vm, const ClassInfo* classInfo, const HashTableValue (&values)[numberOfValues], JSObject& thisObj)
{
    BatchedTransitionOptimizer transitionOptimizer(vm, &thisObj);
    for (auto& value : values) {
        if (value.m_key.isNull())
            continue;
        auto key = Identifier::fromString(vm, value.m_key);
        reifyStaticProperty(vm, classInfo, key, value, thisObj);
    }
}

} // namespace JSC
