/*
 *  Copyright (C) 2005, 2007, 2008 Apple Inc. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 *
 */

#ifndef PropertySlot_h
#define PropertySlot_h

#include "JSValue.h"
#include "PropertyName.h"
#include "PropertyOffset.h"
#include <wtf/Assertions.h>
#include <wtf/ForbidHeapAllocation.h>
#include <wtf/NotFound.h>

namespace JSC {

class ExecState;
class JSGlobalData;
class GetterSetter;
class JSObject;

// ECMA 262-3 8.6.1
// Property attributes
enum Attribute {
	None              = 0,
	ReadOnly          = 1 << 1,  // property can be only read, not written
	DontEnum          = 1 << 2,  // property doesn't appear in (for .. in ..)
	DontDelete        = 1 << 3,  // property can't be deleted
	Accessor          = 1 << 4,  // property is a getter/setter
	CustomAccessor    = 1 << 5,
    CustomValue       = 1 << 6,
    CustomAccessorOrValue = CustomAccessor | CustomValue,

	// Things that are used by static hashtables are not in the attributes byte in PropertyMapEntry.
	Function          = 1 << 8,  // property is a function - only used by static hashtables
	Builtin           = 1 << 9,  // property is a builtin function - only used by static hashtables
	ConstantInteger   = 1 << 10, // property is a constant integer - only used by static hashtables
	BuiltinOrFunction = Builtin | Function, // helper only used by static hashtables
	BuiltinOrFunctionOrAccessor = Builtin | Function | Accessor, // helper only used by static hashtables
	BuiltinOrFunctionOrAccessorOrConstant = Builtin | Function | Accessor | ConstantInteger, // helper only used by static hashtables
};

enum CacheabilityType : uint8_t {
    CachingDisallowed,
    CachingAllowed
};

inline unsigned attributesForStructure(unsigned attributes)
{
    // The attributes that are used just for the static hashtable are at bit 8 and higher.
    return static_cast<uint8_t>(attributes);
}

class PropertySlot {

    // We rely on PropertySlot being stack allocated when used. This is needed
    // because we rely on some of its fields being a GC root. For example, it
    // may be the only thing that points to the CustomGetterSetter property it has.
    WTF_FORBID_HEAP_ALLOCATION;

    enum PropertyType : uint8_t {
        TypeUnset,
        TypeValue,
        TypeGetter,
        TypeCustom,
        TypeCustomAccessor,
    };

public:
    enum class InternalMethodType : uint8_t {
        Get, // [[Get]] internal method in the spec.
        HasProperty, // [[HasProperty]] internal method in the spec.
        GetOwnProperty, // [[GetOwnProperty]] internal method in the spec.
        VMInquiry, // Our VM is just poking around. When this is the InternalMethodType, getOwnPropertySlot is not allowed to do user observable actions.
    };

    explicit ALWAYS_INLINE PropertySlot(const JSValue thisValue, InternalMethodType internalMethodType)
    : m_attributes(0)
    , m_offset(invalidOffset)
    , m_thisValue(thisValue)
    , m_slotBase(0)
    , m_watchpointSet(0)
    , m_cacheability(CachingAllowed)
    , m_propertyType(TypeUnset)
    , m_internalMethodType(internalMethodType)
    , m_isTaintedByOpaqueObject(false)
    {
    }

    // FIXME: Remove this slotBase / receiver behavior difference in custom values and custom accessors.
    // https://bugs.webkit.org/show_bug.cgi?id=158014
    typedef EncodedJSValue (*GetValueFunc)(ExecState*, EncodedJSValue thisValue, PropertyName);

    JSValue getValue(ExecState*, PropertyName) const;
    JSValue getValue(ExecState*, unsigned propertyName) const;
    JSValue getValue(JSGlobalData& vm, ExecState*, unsigned propertyName) const;
    JSValue getPureResult() const;

	bool isCacheable() const { return m_cacheability == CachingAllowed && m_offset != invalidOffset; }
	bool isUnset() const { return m_propertyType == TypeUnset; }
	bool isValue() const { return m_propertyType == TypeValue; }
	bool isAccessor() const { return m_propertyType == TypeGetter; }
	bool isCustom() const { return m_propertyType == TypeCustom; }
	bool isCustomAccessor() const { return m_propertyType == TypeCustomAccessor; }
	bool isCacheableValue() const { return isCacheable() && isValue(); }
	bool isCacheableGetter() const { return isCacheable() && isAccessor(); }
	bool isCacheableCustom() const { return isCacheable() && isCustom(); }
    void setIsTaintedByOpaqueObject() { m_isTaintedByOpaqueObject = true; }
    bool isTaintedByOpaqueObject() const { return m_isTaintedByOpaqueObject; }

    InternalMethodType internalMethodType() const { return m_internalMethodType; }

    void disableCaching()
    {
        m_cacheability = CachingDisallowed;
    }

	ALWAYS_INLINE unsigned attributes() const { return m_attributes; }

	ALWAYS_INLINE PropertyOffset cachedOffset() const
	{
		ASSERT(isCacheable());
		return m_offset;
	}

	ALWAYS_INLINE GetterSetter* getterSetter() const
    {
        ASSERT(isAccessor());
        return m_data.getter.getterSetter;
    }

	ALWAYS_INLINE GetValueFunc customGetter() const
    {
        ASSERT(isCacheableCustom());
        return m_data.custom.getValue;
    }

    CustomGetterSetter* customGetterSetter() const
    {
        ASSERT(isCustomAccessor());
        return m_data.customAccessor.getterSetter;
    }

	ALWAYS_INLINE JSObject* slotBase() const
    {
        return m_slotBase;
    }

    WatchpointSet* watchpointSet() const
    {
        return m_watchpointSet;
    }

	ALWAYS_INLINE void setValue(JSObject* slotBase, unsigned attributes, JSValue value)
    {
        ASSERT(attributes == attributesForStructure(attributes));
        m_data.value = JSValue::encode(value);
        m_attributes = attributes;

        ASSERT(slotBase);
        m_slotBase = slotBase;
        m_propertyType = TypeValue;
        m_offset = invalidOffset;
    }

    ALWAYS_INLINE void setValue(JSObject* slotBase, unsigned attributes, JSValue value, PropertyOffset offset)
    {
        ASSERT(attributes == attributesForStructure(attributes));
        ASSERT(value);
        m_data.value = JSValue::encode(value);
        m_attributes = attributes;

        ASSERT(slotBase);
        m_slotBase = slotBase;
        m_propertyType = TypeValue;
        m_offset = offset;
    }

    ALWAYS_INLINE void setValue(JSString*, unsigned attributes, JSValue value)
    {
        ASSERT(attributes == attributesForStructure(attributes));
        ASSERT(value);
        m_data.value = JSValue::encode(value);
        m_attributes = attributes;

        m_slotBase = 0;
        m_propertyType = TypeValue;
        m_offset = invalidOffset;
    }

    ALWAYS_INLINE void setCustom(JSObject* slotBase, unsigned attributes, GetValueFunc getValue)
    {
        ASSERT(attributes == attributesForStructure(attributes));
        ASSERT(getValue);
        m_data.custom.getValue = getValue;
        m_attributes = attributes;

        ASSERT(slotBase);
        m_slotBase = slotBase;
        m_propertyType = TypeCustom;
        m_offset = invalidOffset;
    }

    ALWAYS_INLINE void setCacheableCustom(JSObject* slotBase, unsigned attributes, GetValueFunc getValue)
    {
        ASSERT(attributes == attributesForStructure(attributes));
        ASSERT(getValue);
        m_data.custom.getValue = getValue;
        m_attributes = attributes;

        ASSERT(slotBase);
        m_slotBase = slotBase;
        m_propertyType = TypeCustom;
        m_offset = !invalidOffset;
    }

    void setCustomGetterSetter(JSObject* slotBase, unsigned attributes, CustomGetterSetter* getterSetter)
    {
        ASSERT(attributes == attributesForStructure(attributes));
        ASSERT(attributes & Attribute::CustomAccessor);

        disableCaching();

        ASSERT(getterSetter);
        m_data.customAccessor.getterSetter = getterSetter;
        m_attributes = attributes;

        ASSERT(slotBase);
        m_slotBase = slotBase;
        m_propertyType = TypeCustomAccessor;
        m_offset = invalidOffset;
    }

    ALWAYS_INLINE void setGetterSlot(JSObject* slotBase, unsigned attributes, GetterSetter* getterSetter)
    {
        ASSERT(attributes == attributesForStructure(attributes));
        ASSERT(getterSetter);
        m_data.getter.getterSetter = getterSetter;
        m_attributes = attributes;

        ASSERT(slotBase);
        m_slotBase = slotBase;
        m_propertyType = TypeGetter;
        m_offset = invalidOffset;
    }

    ALWAYS_INLINE void setCacheableGetterSlot(JSObject* slotBase, unsigned attributes, GetterSetter* getterSetter, PropertyOffset offset)
    {
        ASSERT(attributes == attributesForStructure(attributes));
        ASSERT(getterSetter);
        m_data.getter.getterSetter = getterSetter;
        m_attributes = attributes;

        ASSERT(slotBase);
        m_slotBase = slotBase;
        m_propertyType = TypeGetter;
        m_offset = offset;
    }

    JSValue thisValue() const
    {
        return m_thisValue;
    }

	ALWAYS_INLINE void setUndefined()
	{
        m_data.value = JSValue::encode(jsUndefined());
        m_attributes = ReadOnly | DontDelete | DontEnum;

        m_slotBase = 0;
        m_propertyType = TypeValue;
        m_offset = invalidOffset;
	}

    void setWatchpointSet(WatchpointSet& set)
    {
        m_watchpointSet = &set;
    }

private:
	JS_EXPORT_PRIVATE JSValue functionGetter(ExecState*) const;
    JS_EXPORT_PRIVATE JSValue customGetter(ExecState*, PropertyName) const;
    JS_EXPORT_PRIVATE JSValue customAccessorGetter(ExecState*, PropertyName) const;

    unsigned m_attributes;
	union {
		EncodedJSValue value;
		struct {
			GetterSetter* getterSetter;
		} getter;
		struct {
			GetValueFunc getValue;
		} custom;
        struct {
            CustomGetterSetter* getterSetter;
        } customAccessor;
	} m_data;

	PropertyOffset m_offset;
	JSValue m_thisValue;
    JSObject* m_slotBase;
    WatchpointSet* m_watchpointSet;
    CacheabilityType m_cacheability;
    PropertyType m_propertyType;
    InternalMethodType m_internalMethodType;
    bool m_isTaintedByOpaqueObject;
};

ALWAYS_INLINE JSValue PropertySlot::getValue(ExecState* exec, PropertyName propertyName) const
{
    if (m_propertyType == TypeValue)
        return JSValue::decode(m_data.value);
    if (m_propertyType == TypeGetter)
        return functionGetter(exec);
    if (m_propertyType == TypeCustomAccessor)
        return customAccessorGetter(exec, propertyName);
    return customGetter(exec, propertyName);
}

ALWAYS_INLINE JSValue PropertySlot::getValue(JSGlobalData& vm, ExecState* exec, unsigned propertyName) const
{
    if (m_propertyType == TypeValue)
        return JSValue::decode(m_data.value);
    if (m_propertyType == TypeGetter)
        return functionGetter(exec);
    return customGetter(exec, Identifier::from(vm, propertyName));
}

} // namespace JSC

#endif // PropertySlot_h
