//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Copyright (c) ChakraCore Project Contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeLibraryPch.h"

using namespace Js;

Var JavascriptObject::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();
    JavascriptLibrary* library = scriptContext->GetLibrary();

    AssertMsg(args.HasArg(), "Should always have implicit 'this'");

    Var newTarget = args.GetNewTarget();
    if (JavascriptOperators::GetAndAssertIsConstructorSuperCall(args) &&
        newTarget != function)
    {
        return JavascriptOperators::OrdinaryCreateFromConstructor(
            VarTo<RecyclableObject>(newTarget),
            library->CreateObject(true),
            nullptr,
            scriptContext);
    }

    Var arg = args.Info.Count > 1 ? args[1] : library->GetUndefined();
    switch (JavascriptOperators::GetTypeId(arg))
    {
        case TypeIds_Undefined:
        case TypeIds_Null:
            // Null and undefined result in a new object
            return (callInfo.Flags & CallFlags_NotUsed)
                ? arg
                : library->CreateObject(true);

        case TypeIds_StringObject:
        case TypeIds_Function:
        case TypeIds_Array:
        case TypeIds_ES5Array:
        case TypeIds_RegEx:
        case TypeIds_NumberObject:
        case TypeIds_SIMDObject:
        case TypeIds_Date:
        case TypeIds_BooleanObject:
        case TypeIds_Error:
        case TypeIds_Object:
        case TypeIds_Arguments:
        case TypeIds_ActivationObject:
        case TypeIds_SymbolObject:
            // Since we know this is an object, we can skip ToObject
            return arg;
    }

    RecyclableObject* result = nullptr;
    JavascriptConversion::ToObject(arg, scriptContext, &result);
    Assert(result);
    return result;
}

Var JavascriptObject::EntryHasOwnProperty(RecyclableObject* function, CallInfo callInfo, ...)
{
    JIT_HELPER_REENTRANT_HEADER(Object_HasOwnProperty);
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");

    RecyclableObject* dynamicObject = nullptr;
    if (FALSE == JavascriptConversion::ToObject(args[0], scriptContext, &dynamicObject))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("Object.prototype.hasOwnProperty"));
    }

    Var propertyName = args.Info.Count == 1 ? scriptContext->GetLibrary()->GetUndefined() : args[1];

    const PropertyRecord* propertyRecord;
    PropertyString* propertyString;
    JavascriptConversion::ToPropertyKey(propertyName, scriptContext, &propertyRecord, &propertyString);

    if (JavascriptOperators::HasOwnProperty(dynamicObject, propertyRecord->GetPropertyId(), scriptContext, propertyString))
    {
        return scriptContext->GetLibrary()->GetTrue();
    }

    return scriptContext->GetLibrary()->GetFalse();
    JIT_HELPER_END(Object_HasOwnProperty);
}

Var JavascriptObject::EntryHasOwn(RecyclableObject* function, CallInfo callInfo, ...)
{
    JIT_HELPER_REENTRANT_HEADER(Object_HasOwn);
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    RecyclableObject* dynamicObject = nullptr;
    // first parameter must exist and be an object coercible or throw type error
    if (args.Info.Count < 2 || FALSE == JavascriptConversion::ToObject(args[1], scriptContext, &dynamicObject))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, _u("Object.hasOwn"));
    }

    // if there is only one parameter use undefined as the property to query
    Var propertyName = args.Info.Count == 2 ? scriptContext->GetLibrary()->GetUndefined() : args[2];

    const PropertyRecord* propertyRecord;
    PropertyString* propertyString;
    JavascriptConversion::ToPropertyKey(propertyName, scriptContext, &propertyRecord, &propertyString);

    if (JavascriptOperators::HasOwnProperty(dynamicObject, propertyRecord->GetPropertyId(), scriptContext, propertyString))
    {
        return scriptContext->GetLibrary()->GetTrue();
    }

    return scriptContext->GetLibrary()->GetFalse();
    JIT_HELPER_END(Object_HasOwn);
}


Var JavascriptObject::EntryPropertyIsEnumerable(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");

    RecyclableObject* dynamicObject = nullptr;
    if (FALSE == JavascriptConversion::ToObject(args[0], scriptContext, &dynamicObject))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("Object.prototype.propertyIsEnumerable"));
    }

    if (args.Info.Count >= 2)
    {
        const PropertyRecord* propertyRecord;
        JavascriptConversion::ToPropertyKey(args[1], scriptContext, &propertyRecord, nullptr);
        PropertyId propertyId = propertyRecord->GetPropertyId();

        PropertyDescriptor currentDescriptor;
        BOOL isCurrentDescriptorDefined = JavascriptOperators::GetOwnPropertyDescriptor(dynamicObject, propertyId, scriptContext, &currentDescriptor);
        if (isCurrentDescriptorDefined == TRUE)
        {
            if (currentDescriptor.IsEnumerable())
            {
                return scriptContext->GetLibrary()->GetTrue();
            }
        }
    }
    return scriptContext->GetLibrary()->GetFalse();
}

BOOL JavascriptObject::ChangePrototype(RecyclableObject* object, RecyclableObject* newPrototype, bool shouldThrow, ScriptContext* scriptContext)
{
    // 8.3.2    [[SetInheritance]] (V)
    // When the [[SetInheritance]] internal method of O is called with argument V the following steps are taken:
    // 1.   Assert: Either Type(V) is Object or Type(V) is Null.
    Assert(JavascriptOperators::IsObject(object));
    Assert(JavascriptOperators::IsObjectOrNull(newPrototype));

    if (VarIs<JavascriptProxy>(object))
    {
        JavascriptProxy* proxy = VarTo<JavascriptProxy>(object);
        CrossSite::ForceCrossSiteThunkOnPrototypeChain(newPrototype);
        return proxy->SetPrototypeTrap(newPrototype, shouldThrow, scriptContext);
    }

    // 2.   Let extensible be the value of the [[Extensible]] internal data property of O.
    // 3.   Let current be the value of the [[Prototype]] internal data property of O.
    // 4.   If SameValue(V, current), then return true.
    if (newPrototype == JavascriptObject::GetPrototypeOf(object, scriptContext))
    {
        return TRUE;
    }

    // 5.   If extensible is false, then return false.
    if (!object->IsExtensible())
    {
        if (shouldThrow)
        {
            JavascriptError::ThrowTypeError(scriptContext, JSERR_NonExtensibleObject);
        }
        return FALSE;
    }

    if (object->IsProtoImmutable())
    {
        // ES2016 19.1.3:
        // The Object prototype object is the intrinsic object %ObjectPrototype%.
        // The Object prototype object is an immutable prototype exotic object.
        // ES2016 9.4.7:
        // An immutable prototype exotic object is an exotic object that has an immutable [[Prototype]] internal slot.
        JavascriptError::ThrowTypeError(scriptContext, JSERR_ImmutablePrototypeSlot);
    }

    // 6.   If V is not null, then
    //  a.  Let p be V.
    //  b.  Repeat, while p is not null
    //      i.  If SameValue(p, O) is true, then return false.
    //      ii. Let nextp be the result of calling the [[GetInheritance]] internal method of p with no arguments.
    //      iii.    ReturnIfAbrupt(nextp).
    //      iv. Let  p be nextp.
    if (IsPrototypeOfStopAtProxy(object, newPrototype, scriptContext)) // Reject cycle
    {
        if (shouldThrow)
        {
            JavascriptError::ThrowTypeError(scriptContext, JSERR_CyclicProtoValue);
        }
        return FALSE;
    }

    // 7.   Set the value of the [[Prototype]] internal data property of O to V.
    // 8.   Return true.

    bool isInvalidationOfInlineCacheNeeded = true;
    DynamicObject * obj = VarTo<DynamicObject>(object);

    // If this object was not prototype object, then no need to invalidate inline caches.
    // Simply assign it a new type so if this object used protoInlineCache in past, it will
    // be invalidated because of type mismatch and subsequently we will update its protoInlineCache
    if (!(obj->GetDynamicType()->GetTypeHandler()->GetFlags() & DynamicTypeHandler::IsPrototypeFlag))
    {
        // If object has locked type, skip changing its type here as it will be changed anyway below
        // when object gets newPrototype object.
        if (!obj->HasLockedType())
        {
            obj->ChangeType();
        }
        Assert(!obj->GetScriptContext()->GetThreadContext()->IsObjectRegisteredInProtoInlineCaches(obj));
        Assert(!obj->GetScriptContext()->GetThreadContext()->IsObjectRegisteredInStoreFieldInlineCaches(obj));
        isInvalidationOfInlineCacheNeeded = false;
    }

    if (isInvalidationOfInlineCacheNeeded)
    {
        bool allProtoCachesInvalidated = false;

        JavascriptOperators::MapObjectAndPrototypes<true>(newPrototype, [&](RecyclableObject* obj)
        {
            obj->ClearProtoCachesWereInvalidated();
        });

        // Notify old prototypes that they are being removed from a prototype chain. This triggers invalidating protocache, etc.
        JavascriptOperators::MapObjectAndPrototypesUntil<true>(object->GetPrototype(), [&](RecyclableObject* obj)->bool
        {
            obj->RemoveFromPrototype(scriptContext, &allProtoCachesInvalidated);
            return allProtoCachesInvalidated;
        });

        // Examine new prototype chain. If it brings in any special property, we need to invalidate related caches.
        bool objectAndPrototypeChainHasNoSpecialProperties =
            JavascriptOperators::CheckIfObjectAndProtoChainHasNoSpecialProperties(newPrototype);

        if (!objectAndPrototypeChainHasNoSpecialProperties
            || object->GetScriptContext() != newPrototype->GetScriptContext())
        {
            // The HaveNoSpecialProperties cache is cleared when a property is added or changed,
            // but only for types in the same script context. Therefore, if the prototype is in another
            // context, the object's cache won't be cleared when a property is added or changed on the prototype.
            // Moreover, an object is added to the cache only when its whole prototype chain is in the same
            // context.
            //
            // Since we don't have a way to find out which objects have a certain object as their prototype,
            // we clear the cache here instead.
            object->GetLibrary()->GetTypesWithNoSpecialPropertyProtoChainCache()->Clear();
        }

        // Examine new prototype chain. If it brings in any non-WritableData property, we need to invalidate related caches.
        bool objectAndPrototypeChainHasOnlyWritableDataProperties =
            JavascriptOperators::CheckIfObjectAndPrototypeChainHasOnlyWritableDataProperties(newPrototype);

        if (!objectAndPrototypeChainHasOnlyWritableDataProperties
            || object->GetScriptContext() != newPrototype->GetScriptContext())
        {
            // The HaveOnlyWritableDataProperties cache is cleared when a property is added or changed,
            // but only for types in the same script context. Therefore, if the prototype is in another
            // context, the object's cache won't be cleared when a property is added or changed on the prototype.
            // Moreover, an object is added to the cache only when its whole prototype chain is in the same
            // context.
            //
            // Since we don't have a way to find out which objects have a certain object as their prototype,
            // we clear the cache here instead.

            // Invalidate fast prototype chain writable data test flag
            object->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
        }

        if (!allProtoCachesInvalidated)
        {
            // Invalidate StoreField/PropertyGuards for any non-WritableData property in the new chain
            JavascriptOperators::MapObjectAndPrototypesUntil<true>(newPrototype, [&](RecyclableObject* obj)->bool
            {
                obj->AddToPrototype(scriptContext, &allProtoCachesInvalidated);
                return allProtoCachesInvalidated;
            });
        }

        JavascriptOperators::MapObjectAndPrototypesUntil<true>(object->GetPrototype(), [](RecyclableObject* obj)->bool
        {
            return obj->ClearProtoCachesWereInvalidated();
        });
    }

    // Set to new prototype
    if (object->IsExternal() || (DynamicType::Is(object->GetTypeId()) && (UnsafeVarTo<DynamicObject>(object))->IsCrossSiteObject()))
    {
        CrossSite::ForceCrossSiteThunkOnPrototypeChain(newPrototype);
    }
    object->SetPrototype(newPrototype);
    return TRUE;
}

Var JavascriptObject::EntryIsPrototypeOf(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");

    // no property specified
    if (args.Info.Count == 1 || !JavascriptOperators::IsObject(args[1]))
    {
        return scriptContext->GetLibrary()->GetFalse();
    }

    RecyclableObject* dynamicObject = nullptr;
    if (FALSE == JavascriptConversion::ToObject(args[0], scriptContext, &dynamicObject))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("Object.prototype.isPrototypeOf"));
    }
    RecyclableObject* value = VarTo<RecyclableObject>(args[1]);

    if (dynamicObject->GetTypeId() == TypeIds_GlobalObject)
    {
        dynamicObject = VarTo<RecyclableObject>(static_cast<Js::GlobalObject*>(dynamicObject)->ToThis());
    }

    while (!JavascriptOperators::IsNull(value))
    {
        value = JavascriptOperators::GetPrototype(value);
        if (dynamicObject == value)
        {
            return scriptContext->GetLibrary()->GetTrue();
        }
    }

    return scriptContext->GetLibrary()->GetFalse();
}

// 19.1.3.5 - Object.prototype.toLocaleString as of ES6 (6.0)
Var JavascriptObject::EntryToLocaleString(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();
    Assert(!(callInfo.Flags & CallFlags_New));
    AssertMsg(args.Info.Count, "Should always have implicit 'this'");

    Var thisValue = args[0];
    RecyclableObject* dynamicObject = nullptr;

    if (FALSE == JavascriptConversion::ToObject(thisValue, scriptContext, &dynamicObject))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("Object.prototype.toLocaleString"));
    }

    Var toStringVar = nullptr;
    if (!JavascriptOperators::GetProperty(thisValue, dynamicObject, Js::PropertyIds::toString, &toStringVar, scriptContext) || !JavascriptConversion::IsCallable(toStringVar))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedFunction, _u("Object.prototype.toLocaleString"));
    }

    RecyclableObject* toStringFunc = VarTo<RecyclableObject>(toStringVar);
    if (toStringFunc == scriptContext->GetLibrary()->GetObjectToStringFunction())
    {
        return ToStringHelper(thisValue, scriptContext);
    }
    else
    {
        return scriptContext->GetThreadContext()->ExecuteImplicitCall(toStringFunc, Js::ImplicitCall_ToPrimitive, [=]()->Js::Var
        {
            return CALL_FUNCTION(scriptContext->GetThreadContext(), toStringFunc, CallInfo(CallFlags_Value, 1), thisValue);
        });
    }
}

Var JavascriptObject::EntryToString(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    AssertMsg(args.Info.Count, "Should always have implicit 'this'");
    return ToStringHelper(args[0], scriptContext);
}

Var JavascriptObject::GetToStringTagValue(RecyclableObject *thisArg, ScriptContext *scriptContext)
{
    if (JavascriptOperators::CheckIfObjectAndProtoChainHasNoSpecialProperties(thisArg))
    {
        return nullptr;
    }

    const PropertyId toStringTagId(PropertyIds::_symbolToStringTag);
    PolymorphicInlineCache *cache = scriptContext->GetLibrary()->GetToStringTagCache();
    PropertyValueInfo info;
    // We don't allow cache resizing, at least for the moment: it's more work, and since there's only one
    // cache per script context, we can afford to create each cache with the maximum size.
    PropertyValueInfo::SetCacheInfo(&info, cache, false);
    Var value;
    if (CacheOperators::TryGetProperty<
        true,                                       // CheckLocal
        true,                                       // CheckProto
        true,                                       // CheckAccessor
        true,                                       // CheckMissing
        true,                                       // CheckPolymorphicInlineCache
        true,                                       // CheckTypePropertyCache
        !PolymorphicInlineCache::IsPolymorphic,     // IsInlineCacheAvailable
        PolymorphicInlineCache::IsPolymorphic,      // IsPolymorphicInlineCacheAvailable
        false,                                      // ReturnOperationInfo
        false>                                      // OutputExistence
        (thisArg, false, thisArg, toStringTagId, &value, scriptContext, nullptr, &info))
    {
        return value;
    }
    else
    {
#if DBG_DUMP
        if (PHASE_VERBOSE_TRACE1(Js::InlineCachePhase))
        {
            CacheOperators::TraceCache(cache, _u("PatchGetValue"), toStringTagId, scriptContext, thisArg);
        }
#endif
        return JavascriptOperators::GetProperty(thisArg, thisArg, toStringTagId, scriptContext, &info);
    }
}

// ES2017 19.1.3.6 Object.prototype.toString()
JavascriptString* JavascriptObject::ToStringTagHelper(Var thisArg, ScriptContext *scriptContext, TypeId type)
{
    JavascriptLibrary *library = scriptContext->GetLibrary();

    // 1. If the this value is undefined, return "[object Undefined]".
    if (type == TypeIds_Undefined)
    {
        return library->GetObjectUndefinedDisplayString();
    }
    // 2. If the this value is null, return "[object Null]".
    if (type == TypeIds_Null)
    {
        return library->GetObjectNullDisplayString();
    }

    // 3. Let O be ToObject(this value).
    RecyclableObject *thisArgAsObject = JavascriptOperators::ToObject(thisArg, scriptContext);

    // 15. Let tag be ? Get(O, @@toStringTag).
    Var tag = JavascriptObject::GetToStringTagValue(thisArgAsObject, scriptContext);

    // 17. Return the String that is the result of concatenating "[object ", tag, and "]".
    auto buildToString = [&scriptContext](Var tag) {
        JavascriptString *tagStr = VarTo<JavascriptString>(tag);
        const WCHAR objectStartString[9] = _u("[object ");
        const WCHAR objectEndString[1] = { _u(']') };
        CompoundString *const cs = CompoundString::NewWithCharCapacity(_countof(objectStartString)
            + _countof(objectEndString) + tagStr->GetLength(), scriptContext->GetLibrary());

        cs->AppendChars(objectStartString, _countof(objectStartString) - 1 /* ditch \0 */);
        cs->AppendChars(tagStr);
        cs->AppendChars(objectEndString, _countof(objectEndString));

        return cs;
    };
    if (tag != nullptr && VarIs<JavascriptString>(tag))
    {
        return buildToString(tag);
    }

    // 4. Let isArray be ? IsArray(O).
    // There is an implicit check for a null proxy handler in IsArray, so use the operator.
    BOOL isArray = JavascriptOperators::IsArray(thisArgAsObject);

    // If we don't have a tag or it's not a string, use the 'built in tag'.
    if (isArray)
    {
        // 5. If isArray is true, let builtinTag be "Array".
        return library->GetObjectArrayDisplayString();
    }

        // callable proxy is considered as having [[Call]] internal method and should match #8 below
        if (type == TypeIds_Proxy && JavascriptConversion::IsCallable(thisArgAsObject))
        {
            type = TypeIds_Function;
        }

    JavascriptString* builtInTag = nullptr;
    switch (type)
    {
        // 6. Else if O is an exotic String object, let builtinTag be "String".
    case TypeIds_String:
    case TypeIds_StringObject:
        builtInTag = library->GetObjectStringDisplayString();
        break;

        // 7. Else if O has an[[ParameterMap]] internal slot, let builtinTag be "Arguments".
    case TypeIds_Arguments:
        builtInTag = library->GetObjectArgumentsDisplayString();
        break;

        // 8. Else if O has a [[Call]] internal method, let builtinTag be "Function".
    case TypeIds_Function:
        builtInTag = library->GetObjectFunctionDisplayString();
        break;

        // 9. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error".
    case TypeIds_Error:
        builtInTag = library->GetObjectErrorDisplayString();
        break;

        // 10. Else if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean".
    case TypeIds_Boolean:
    case TypeIds_BooleanObject:
        builtInTag = library->GetObjectBooleanDisplayString();
        break;

        // 11. Else if O has a [[NumberData]] internal slot, let builtinTag be "Number".
    case TypeIds_Number:
    case TypeIds_Int64Number:
    case TypeIds_UInt64Number:
    case TypeIds_Integer:
    case TypeIds_NumberObject:
        builtInTag = library->GetObjectNumberDisplayString();
        break;

        // 12. Else if O has a [[DateValue]] internal slot, let builtinTag be "Date".
    case TypeIds_Date:
        builtInTag = library->GetObjectDateDisplayString();
        break;

        // 13. Else if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp".
    case TypeIds_RegEx:
        builtInTag = library->GetObjectRegExpDisplayString();
        break;

        // 14. Else, let builtinTag be "Object".
    default:
    {
        if (thisArgAsObject->IsExternal())
        {
            builtInTag = buildToString(thisArgAsObject->GetClassName(scriptContext));
        }
        else
        {
            builtInTag = library->GetObjectDisplayString(); // [object Object]
        }
        break;
    }
    }

    Assert(builtInTag != nullptr);

    return builtInTag;
}

Var JavascriptObject::ToStringHelper(Var thisArg, ScriptContext* scriptContext)
{
    TypeId type = JavascriptOperators::GetTypeId(thisArg);

    // We first need to make sure we are in the right context.
    if (type == TypeIds_HostDispatch)
    {
        RecyclableObject* hostDispatchObject = VarTo<RecyclableObject>(thisArg);
        const DynamicObject* remoteObject = hostDispatchObject->GetRemoteObject();
        if (!remoteObject)
        {
            Var result = nullptr;
            Js::Var values[1];
            Js::CallInfo info(Js::CallFlags_Value, 1);
            Js::Arguments args(info, values);
            values[0] = thisArg;
            if (hostDispatchObject->InvokeBuiltInOperationRemotely(EntryToString, args, &result))
            {
                return result;
            }
        }
    }

    // Dispatch to @@toStringTag implementation.
    if (type >= TypeIds_TypedArrayMin && type <= TypeIds_TypedArrayMax && !scriptContext->GetThreadContext()->IsScriptActive())
    {
        // Use external call for typedarray in the debugger.
        Var toStringValue = nullptr;
        BEGIN_JS_RUNTIME_CALL_EX(scriptContext, false);
        toStringValue = ToStringTagHelper(thisArg, scriptContext, type);
        END_JS_RUNTIME_CALL(scriptContext);
        return toStringValue;
    }

    // By this point, we should be in the correct context, but the thisArg may still need to be marshalled (for to the implicit ToObject conversion call.)
    return ToStringTagHelper(CrossSite::MarshalVar(scriptContext, thisArg), scriptContext, type);
}

// -----------------------------------------------------------
// Object.prototype.valueOf
//    1.    Let O be the result of calling ToObject passing the this value as the argument.
//    2.    If O is the result of calling the Object constructor with a host object (15.2.2.1), then
//    a.    Return either O or another value such as the host object originally passed to the constructor. The specific result that is returned is implementation-defined.
//    3.    Return O.
// -----------------------------------------------------------

Var JavascriptObject::EntryValueOf(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");

    // throw a TypeError if TypeId is null or undefined, and apply ToObject to the 'this' value otherwise.
    if (JavascriptOperators::IsUndefinedOrNull(args[0]))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("Object.prototype.valueOf"));
    }
    else
    {
        return JavascriptOperators::ToObject(args[0], scriptContext);
    }
}

Var JavascriptObject::EntryGetOwnPropertyDescriptor(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    RecyclableObject* obj = nullptr;
    if (args.Info.Count < 2)
    {
        obj = JavascriptOperators::ToObject(scriptContext->GetLibrary()->GetUndefined(), scriptContext);
    }
    else
    {
        // Convert the argument to object first
        obj = JavascriptOperators::ToObject(args[1], scriptContext);
    }

    // If the object is HostDispatch try to invoke the operation remotely
    if (obj->GetTypeId() == TypeIds_HostDispatch)
    {
        Var result;
        if (obj->InvokeBuiltInOperationRemotely(EntryGetOwnPropertyDescriptor, args, &result))
        {
            return result;
        }
    }

    Var propertyKey = args.Info.Count > 2 ? args[2] : obj->GetLibrary()->GetUndefined();

    return JavascriptObject::GetOwnPropertyDescriptorHelper(obj, propertyKey, scriptContext);
}

Var JavascriptObject::GetOwnPropertyDescriptorHelper(RecyclableObject* obj, Var propertyKey, ScriptContext* scriptContext)
{
    const PropertyRecord* propertyRecord;
    JavascriptConversion::ToPropertyKey(propertyKey, scriptContext, &propertyRecord, nullptr);
    PropertyId propertyId = propertyRecord->GetPropertyId();

    PropertyDescriptor propertyDescriptor;
    BOOL isPropertyDescriptorDefined;
    isPropertyDescriptorDefined = JavascriptObject::GetOwnPropertyDescriptorHelper(obj, propertyId, scriptContext, propertyDescriptor);
    if (!isPropertyDescriptorDefined)
    {
        return scriptContext->GetLibrary()->GetUndefined();
    }

    return JavascriptOperators::FromPropertyDescriptor(propertyDescriptor, scriptContext);
}

BOOL JavascriptObject::GetOwnPropertyDescriptorHelper(RecyclableObject* obj, PropertyId propertyId, ScriptContext* scriptContext, PropertyDescriptor& propertyDescriptor)
{
    BOOL isPropertyDescriptorDefined;
    if (obj->IsExternal())
    {
        isPropertyDescriptorDefined = obj->HasOwnProperty(propertyId) ?
            JavascriptOperators::GetOwnPropertyDescriptor(obj, propertyId, scriptContext, &propertyDescriptor) :
            FALSE;
    }
    else
    {
        isPropertyDescriptorDefined = JavascriptOperators::GetOwnPropertyDescriptor(obj, propertyId, scriptContext, &propertyDescriptor);
    }
    return isPropertyDescriptorDefined;
}

Var JavascriptObject::EntryGetOwnPropertyDescriptors(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    RecyclableObject* obj = nullptr;

    if (args.Info.Count < 2)
    {
        obj = JavascriptOperators::ToObject(scriptContext->GetLibrary()->GetUndefined(), scriptContext);
    }
    else
    {
        // Convert the argument to object first
        obj = JavascriptOperators::ToObject(args[1], scriptContext);
    }

    // If the object is HostDispatch try to invoke the operation remotely
    if (obj->GetTypeId() == TypeIds_HostDispatch)
    {
        Var result;
        if (obj->InvokeBuiltInOperationRemotely(EntryGetOwnPropertyDescriptors, args, &result))
        {
            return result;
        }
    }

    JavascriptArray* ownPropertyKeys = JavascriptOperators::GetOwnPropertyKeys(obj, scriptContext);
    RecyclableObject* resultObj = scriptContext->GetLibrary()->CreateObject(true, (Js::PropertyIndex) ownPropertyKeys->GetLength());

    PropertyDescriptor propDesc;
    Var propKey = nullptr;

    for (uint i = 0; i < ownPropertyKeys->GetLength(); i++)
    {
        BOOL getPropResult = ownPropertyKeys->DirectGetItemAt(i, &propKey);
        Assert(getPropResult);

        if (!getPropResult)
        {
            continue;
        }

        PropertyRecord const * propertyRecord;
        JavascriptConversion::ToPropertyKey(propKey, scriptContext, &propertyRecord, nullptr);

        Var newDescriptor = JavascriptObject::GetOwnPropertyDescriptorHelper(obj, propKey, scriptContext);
        if (!JavascriptOperators::IsUndefined(newDescriptor))
        {
            resultObj->SetProperty(propertyRecord->GetPropertyId(), newDescriptor, PropertyOperation_None, nullptr);
        }
    }

    return resultObj;
}

Var JavascriptObject::EntryGetPrototypeOf(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));
    CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Object_Constructor_getPrototypeOf);

    // 19.1.2.9
    // Object.getPrototypeOf ( O )
    // When the getPrototypeOf function is called with argument O, the following steps are taken:
    RecyclableObject *object = nullptr;

    // 1. Let obj be ToObject(O).
    // 2. ReturnIfAbrupt(obj).
    if (args.Info.Count < 2 || !JavascriptConversion::ToObject(args[1], scriptContext, &object))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, _u("Object.getPrototypeOf"));
    }

    // 3. Return obj.[[GetPrototypeOf]]().
    return CrossSite::MarshalVar(scriptContext, GetPrototypeOf(object, scriptContext));
}

Var JavascriptObject::EntrySetPrototypeOf(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    Assert(!(callInfo.Flags & CallFlags_New));
    ScriptContext* scriptContext = function->GetScriptContext();

    // 19.1.2.18
    // Object.setPrototypeOf ( O, proto )
    // When the setPrototypeOf function is called with arguments O and proto, the following steps are taken:
    // 1. Let O be RequireObjectCoercible(O).
    // 2. ReturnIfAbrupt(O).
    // 3. If Type(proto) is neither Object or Null, then throw a TypeError exception.
    int32 errCode = NOERROR;

    if (args.Info.Count < 2 || !JavascriptConversion::CheckObjectCoercible(args[1], scriptContext))
    {
        errCode = JSERR_FunctionArgument_NeedObject;
    }
    else if (args.Info.Count < 3 || !JavascriptOperators::IsObjectOrNull(args[2]))
    {
        errCode = JSERR_FunctionArgument_NotObjectOrNull;
    }

    if (errCode != NOERROR)
    {
        JavascriptError::ThrowTypeError(scriptContext, errCode, _u("Object.setPrototypeOf"));
    }

    // 4. If Type(O) is not Object, return O.
    if (!JavascriptOperators::IsObject(args[1]))
    {
        return args[1];
    }

#if ENABLE_COPYONACCESS_ARRAY
    JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(args[1]);
#endif
    RecyclableObject* object = VarTo<RecyclableObject>(args[1]);
    RecyclableObject* newPrototype = VarTo<RecyclableObject>(args[2]);

    // 5. Let status be O.[[SetPrototypeOf]](proto).
    // 6. ReturnIfAbrupt(status).
    // 7. If status is false, throw a TypeError exception.
    ChangePrototype(object, newPrototype, /*shouldThrow*/true, scriptContext);

    // 8. Return O.
    return object;
}

Var JavascriptObject::EntrySeal(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Object_Constructor_seal);

    // Spec update in Rev29 under section 19.1.2.17
    if (args.Info.Count < 2)
    {
        return scriptContext->GetLibrary()->GetUndefined();
    }
    else if (!JavascriptOperators::IsObject(args[1]))
    {
        return args[1];
    }


    RecyclableObject *object = VarTo<RecyclableObject>(args[1]);

    GlobalObject* globalObject = object->GetLibrary()->GetGlobalObject();
    if (globalObject != object && globalObject && (globalObject->ToThis() == object))
    {
        globalObject->Seal();
    }

    object->Seal();
    return object;
}

Var JavascriptObject::EntryFreeze(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));
    CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Object_Constructor_freeze);

    // Spec update in Rev29 under section 19.1.2.5
    if (args.Info.Count < 2)
    {
        return scriptContext->GetLibrary()->GetUndefined();
    }
    else if (!JavascriptOperators::IsObject(args[1]))
    {
        return args[1];
    }

    RecyclableObject *object = VarTo<RecyclableObject>(args[1]);

    GlobalObject* globalObject = object->GetLibrary()->GetGlobalObject();
    if (globalObject != object && globalObject && (globalObject->ToThis() == object))
    {
        globalObject->Freeze();
    }

    object->Freeze();
    return object;
}

Var JavascriptObject::EntryPreventExtensions(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));
    CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Object_Constructor_preventExtensions);

    // Spec update in Rev29 under section 19.1.2.15
    if (args.Info.Count < 2)
    {
        return scriptContext->GetLibrary()->GetUndefined();
    }
    else if (!JavascriptOperators::IsObject(args[1]))
    {
        return args[1];
    }

    RecyclableObject *object = VarTo<RecyclableObject>(args[1]);

    GlobalObject* globalObject = object->GetLibrary()->GetGlobalObject();
    if (globalObject != object && globalObject && (globalObject->ToThis() == object))
    {
        globalObject->PreventExtensions();
    }

    object->PreventExtensions();

    return object;
}

Var JavascriptObject::EntryIsSealed(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));
    CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Object_Constructor_isSealed);

    if (args.Info.Count < 2 || !JavascriptOperators::IsObject(args[1]))
    {
        return scriptContext->GetLibrary()->GetTrue();
    }

    RecyclableObject *object = VarTo<RecyclableObject>(args[1]);

    BOOL isSealed = object->IsSealed();

    GlobalObject* globalObject = object->GetLibrary()->GetGlobalObject();
    if (isSealed && globalObject != object && globalObject && (globalObject->ToThis() == object))
    {
        isSealed = globalObject->IsSealed();
    }

    return scriptContext->GetLibrary()->GetTrueOrFalse(isSealed);
}

Var JavascriptObject::EntryIsFrozen(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));
    CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Object_Constructor_isFrozen);

    if (args.Info.Count < 2 || !JavascriptOperators::IsObject(args[1]))
    {
        return scriptContext->GetLibrary()->GetTrue();
    }

    RecyclableObject *object = VarTo<RecyclableObject>(args[1]);

    BOOL isFrozen = object->IsFrozen();

    GlobalObject* globalObject = object->GetLibrary()->GetGlobalObject();
    if (isFrozen && globalObject != object && globalObject && (globalObject->ToThis() == object))
    {
        isFrozen = globalObject->IsFrozen();
    }

    return scriptContext->GetLibrary()->GetTrueOrFalse(isFrozen);
}

Var JavascriptObject::EntryIsExtensible(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();
    CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Object_Constructor_isExtensible);

    Assert(!(callInfo.Flags & CallFlags_New));

    if (args.Info.Count < 2 || !JavascriptOperators::IsObject(args[1]))
    {
        return scriptContext->GetLibrary()->GetFalse();
    }

    RecyclableObject *object = VarTo<RecyclableObject>(args[1]);
    BOOL isExtensible = object->IsExtensible();

    GlobalObject* globalObject = object->GetLibrary()->GetGlobalObject();
    if (isExtensible && globalObject != object && globalObject && (globalObject->ToThis() == object))
    {
        isExtensible = globalObject->IsExtensible();
    }

    return scriptContext->GetLibrary()->GetTrueOrFalse(isExtensible);
}

Var JavascriptObject::EntryGetOwnPropertyNames(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));
    CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Object_Constructor_getOwnPropertyNames);

    Var tempVar = args.Info.Count < 2 ? scriptContext->GetLibrary()->GetUndefined() : args[1];
    RecyclableObject *object = JavascriptOperators::ToObject(tempVar, scriptContext);

    if (object->GetTypeId() == TypeIds_HostDispatch)
    {
        Var result;
        if (object->InvokeBuiltInOperationRemotely(EntryGetOwnPropertyNames, args, &result))
        {
            return result;
        }
    }

    return JavascriptOperators::GetOwnPropertyNames(object, scriptContext);
}

Var JavascriptObject::EntryGetOwnPropertySymbols(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    Var tempVar = args.Info.Count < 2 ? scriptContext->GetLibrary()->GetUndefined() : args[1];
    RecyclableObject *object = JavascriptOperators::ToObject(tempVar, scriptContext);

    if (object->GetTypeId() == TypeIds_HostDispatch)
    {
        Var result;
        if (object->InvokeBuiltInOperationRemotely(EntryGetOwnPropertySymbols, args, &result))
        {
            return result;
        }
    }

    return JavascriptOperators::GetOwnPropertySymbols(object, scriptContext);
}

Var JavascriptObject::EntryKeys(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));
    CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Object_Constructor_keys);

    Var tempVar = args.Info.Count < 2 ? scriptContext->GetLibrary()->GetUndefined() : args[1];
    RecyclableObject *object = JavascriptOperators::ToObject(tempVar, scriptContext);

    if (object->GetTypeId() == TypeIds_HostDispatch)
    {
        Var result;
        if (object->InvokeBuiltInOperationRemotely(EntryKeys, args, &result))
        {
            return result;
        }
    }
    return JavascriptOperators::GetOwnEnumerablePropertyNames(object, scriptContext);
}

Var JavascriptObject::GetValuesOrEntries(RecyclableObject* object, bool valuesToReturn, ScriptContext* scriptContext)
{
    Assert(object != nullptr);
    Assert(scriptContext != nullptr);
    JavascriptArray* valuesArray = scriptContext->GetLibrary()->CreateArray(0);

    JavascriptArray* ownKeysResult = JavascriptOperators::GetOwnPropertyNames(object, scriptContext);
    uint32 length = ownKeysResult->GetLength();

    Var nextKey;
    const PropertyRecord* propertyRecord = nullptr;
    PropertyId propertyId;
    for (uint32 i = 0, index = 0; i < length; i++)
    {
        nextKey = ownKeysResult->DirectGetItem(i);
        Assert(VarIs<JavascriptString>(nextKey));

        PropertyDescriptor propertyDescriptor;

        JavascriptConversion::ToPropertyKey(nextKey, scriptContext, &propertyRecord, nullptr);
        propertyId = propertyRecord->GetPropertyId();
        Assert(propertyId != Constants::NoProperty);

        if (JavascriptOperators::GetOwnPropertyDescriptor(object, propertyId, scriptContext, &propertyDescriptor))
        {
            if (propertyDescriptor.IsEnumerable())
            {
                Var value = JavascriptOperators::GetProperty(object, propertyId, scriptContext);

                if (!valuesToReturn)
                {
                    // For Object.entries each entry is key, value pair
                    JavascriptArray* entry = scriptContext->GetLibrary()->CreateArray(2);
                    entry->DirectSetItemAt(0, CrossSite::MarshalVar(scriptContext, nextKey));
                    entry->DirectSetItemAt(1, CrossSite::MarshalVar(scriptContext, value));
                    value = entry;
                }
                valuesArray->DirectSetItemAt(index++, CrossSite::MarshalVar(scriptContext, value));
            }
        }
    }

    return valuesArray;
}

Var JavascriptObject::EntryValues(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));
    CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Object_Constructor_values);

    Var tempVar = args.Info.Count < 2 ? scriptContext->GetLibrary()->GetUndefined() : args[1];
    RecyclableObject *object = JavascriptOperators::ToObject(tempVar, scriptContext);

    return GetValuesOrEntries(object, true /*valuesToReturn*/, scriptContext);
}

Var JavascriptObject::EntryEntries(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));
    CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Object_Constructor_entries);

    Var tempVar = args.Info.Count < 2 ? scriptContext->GetLibrary()->GetUndefined() : args[1];
    RecyclableObject *object = JavascriptOperators::ToObject(tempVar, scriptContext);

    return GetValuesOrEntries(object, false /*valuesToReturn*/, scriptContext);
}

JavascriptArray* JavascriptObject::CreateOwnSymbolPropertiesHelper(RecyclableObject* object, ScriptContext* scriptContext)
{
    return CreateKeysHelper(object, scriptContext, TRUE, true /*includeSymbolsOnly */, false, true /*includeSpecialProperties*/);
}

JavascriptArray* JavascriptObject::CreateOwnStringPropertiesHelper(RecyclableObject* object, ScriptContext* scriptContext)
{
    return CreateKeysHelper(object, scriptContext, TRUE, false, true /*includeStringsOnly*/, true /*includeSpecialProperties*/);
}

JavascriptArray* JavascriptObject::CreateOwnStringSymbolPropertiesHelper(RecyclableObject* object, ScriptContext* scriptContext)
{
    return CreateKeysHelper(object, scriptContext, TRUE, true/*includeSymbolsOnly*/, true /*includeStringsOnly*/, true /*includeSpecialProperties*/);
}

JavascriptArray* JavascriptObject::CreateOwnEnumerableStringPropertiesHelper(RecyclableObject* object, ScriptContext* scriptContext)
{
    return CreateKeysHelper(object, scriptContext, FALSE, false, true/*includeStringsOnly*/, false);
}

JavascriptArray* JavascriptObject::CreateOwnEnumerableStringSymbolPropertiesHelper(RecyclableObject* object, ScriptContext* scriptContext)
{
    return CreateKeysHelper(object, scriptContext, FALSE, true/*includeSymbolsOnly*/, true/*includeStringsOnly*/, false);
}

// 9.1.12 [[OwnPropertyKeys]] () in RC#4 dated April 3rd 2015.
JavascriptArray* JavascriptObject::CreateKeysHelper(RecyclableObject* object, ScriptContext* scriptContext, BOOL includeNonEnumerable, bool includeSymbolProperties, bool includeStringProperties, bool includeSpecialProperties)
{
    //1. Let keys be a new empty List.
    //2. For each own property key P of O that is an integer index, in ascending numeric index order
    //      a. Add P as the last element of keys.
    //3. For each own property key P of O that is a String but is not an integer index, in property creation order
    //      a. Add P as the last element of keys.
    //4. For each own property key P of O that is a Symbol, in property creation order
    //      a. Add P as the last element of keys.
    //5. Return keys.
    AssertMsg(includeStringProperties || includeSymbolProperties, "Should either get string or symbol properties.");

    JavascriptStaticEnumerator enumerator;
    EnumeratorFlags flags = EnumeratorFlags::SnapShotSemantics | EnumeratorFlags::UseCache;
    JavascriptArray* newArr = scriptContext->GetLibrary()->CreateArray(0);
    if (includeNonEnumerable)
    {
        flags |= EnumeratorFlags::EnumNonEnumerable;
    }
    if (includeSymbolProperties)
    {
        flags |= EnumeratorFlags::EnumSymbols;
    }
    EnumeratorCache* cache = scriptContext->GetLibrary()->GetCreateKeysCache(object->GetType());
    if (!object->GetEnumerator(&enumerator, flags, scriptContext, cache))
    {
        return newArr;  // Return an empty array if we don't have an enumerator
    }

    JavascriptString * propertyName = nullptr;
    PropertyId propertyId;
    uint32 propertyIndex = 0;
    uint32 symbolIndex = 0;
    const PropertyRecord* propertyRecord;
    JavascriptSymbol* symbol;
    JavascriptArray* newArrForSymbols = nullptr;
    while ((propertyName = enumerator.MoveAndGetNext(propertyId)) != NULL)
    {
        if (propertyName)
        {
            if (includeSymbolProperties)
            {
                propertyRecord = scriptContext->GetPropertyName(propertyId);

                if (propertyRecord->IsSymbol())
                {
                    symbol = scriptContext->GetSymbol(propertyRecord);
                    // no need to marshal symbol because it is created from scriptContext
                    if (!newArrForSymbols)
                    {
                        newArrForSymbols = scriptContext->GetLibrary()->CreateArray(0);
                    }
                    newArrForSymbols->DirectSetItemAt(symbolIndex++, symbol);
                    continue;
                }
            }
            if (includeStringProperties)
            {
                newArr->DirectSetItemAt(propertyIndex++, CrossSite::MarshalVar(scriptContext, propertyName, propertyName->GetScriptContext()));
            }
        }
    }

    // Special properties
    if (includeSpecialProperties && includeStringProperties)
    {
        uint32 index = 0;
        while (object->GetSpecialPropertyName(index, &propertyName, scriptContext))
        {
            newArr->DirectSetItemAt(propertyIndex++, propertyName);
            index++;
        }
    }

    if (newArrForSymbols)
    {
        // Append all the symbols at the end of list
        uint32 totalSymbols = newArrForSymbols->GetLength();
        for (uint32 symIndex = 0; symIndex < totalSymbols; symIndex++)
        {
            newArr->DirectSetItemAt(propertyIndex++, newArrForSymbols->DirectGetItem(symIndex));
        }
    }

    return newArr;
}

// args[1] this object to operate on.
// args[2] property name.
// args[3] object that attributes for the new descriptor.
Var JavascriptObject::EntryDefineProperty(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    if (args.Info.Count < 2 || !JavascriptOperators::IsObject(args[1]))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, _u("Object.defineProperty"));
    }

#if ENABLE_COPYONACCESS_ARRAY
    JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(args[1]);
#endif
    RecyclableObject* obj = VarTo<RecyclableObject>(args[1]);

    // If the object is HostDispatch try to invoke the operation remotely
    if (obj->GetTypeId() == TypeIds_HostDispatch)
    {
        if (obj->InvokeBuiltInOperationRemotely(EntryDefineProperty, args, NULL))
        {
            return obj;
        }
    }

    Var propertyKey = args.Info.Count > 2 ? args[2] : obj->GetLibrary()->GetUndefined();
    PropertyRecord const * propertyRecord;
    JavascriptConversion::ToPropertyKey(propertyKey, scriptContext, &propertyRecord, nullptr);

    Var descVar = args.Info.Count > 3 ? args[3] : obj->GetLibrary()->GetUndefined();
    PropertyDescriptor propertyDescriptor;
    if (!JavascriptOperators::ToPropertyDescriptor(descVar, &propertyDescriptor, scriptContext))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_PropertyDescriptor_Invalid, scriptContext->GetPropertyName(propertyRecord->GetPropertyId())->GetBuffer());
    }

    if (CONFIG_FLAG(UseFullName))
    {
        ModifyGetterSetterFuncName(propertyRecord, propertyDescriptor, scriptContext);
    }

    BOOL success = DefineOwnPropertyHelper(obj, propertyRecord->GetPropertyId(), propertyDescriptor, scriptContext);
    if (!success)
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_DefineProperty_Default, scriptContext->GetPropertyName(propertyRecord->GetPropertyId())->GetBuffer());
    }

    return obj;
}

Var JavascriptObject::EntryDefineProperties(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Object_Constructor_defineProperties);

    Assert(!(callInfo.Flags & CallFlags_New));

    if (args.Info.Count < 2 || !JavascriptOperators::IsObject(args[1]))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, _u("Object.defineProperties"));
    }

#if ENABLE_COPYONACCESS_ARRAY
    JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(args[1]);
#endif
    RecyclableObject *object = VarTo<RecyclableObject>(args[1]);

    // If the object is HostDispatch try to invoke the operation remotely
    if (object->GetTypeId() == TypeIds_HostDispatch)
    {
        if (object->InvokeBuiltInOperationRemotely(EntryDefineProperties, args, NULL))
        {
            return object;
        }
    }

    Var propertiesVar = args.Info.Count > 2 ? args[2] : object->GetLibrary()->GetUndefined();
    RecyclableObject* properties = nullptr;
    if (FALSE == JavascriptConversion::ToObject(propertiesVar, scriptContext, &properties))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NullOrUndefined, _u("Object.defineProperties"));
    }

    return DefinePropertiesHelper(object, properties, scriptContext);
}

// args[1] property name.
// args[2] function object to use as the getter function.
Var JavascriptObject::EntryDefineGetter(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    // For browser interop, simulate LdThis by calling OP implementation directly.
    // Do not have module id here so use the global id, 0.
    //
#if ENABLE_COPYONACCESS_ARRAY
    JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(args[0]);
#endif
    RecyclableObject* obj = nullptr;
    if (!JavascriptConversion::ToObject(args[0], scriptContext, &obj))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("Object.prototype.__defineGetter__"));
    }

    Var getterFunc = args.Info.Count > 2 ? args[2] : obj->GetLibrary()->GetUndefined();

    if (!JavascriptConversion::IsCallable(getterFunc))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedFunction, _u("Object.prototype.__defineGetter__"));
    }

    Var propertyKey = args.Info.Count > 1 ? args[1] : obj->GetLibrary()->GetUndefined();
    const PropertyRecord* propertyRecord;
    JavascriptConversion::ToPropertyKey(propertyKey, scriptContext, &propertyRecord, nullptr);

    PropertyDescriptor propertyDescriptor;
    propertyDescriptor.SetEnumerable(true);
    propertyDescriptor.SetConfigurable(true);
    propertyDescriptor.SetGetter(getterFunc);

    DefineOwnPropertyHelper(obj, propertyRecord->GetPropertyId(), propertyDescriptor, scriptContext);

    return obj->GetLibrary()->GetUndefined();
}

// args[1] property name.
// args[2] function object to use as the setter function.
Var JavascriptObject::EntryDefineSetter(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    // For browser interop, simulate LdThis by calling OP implementation directly.
    // Do not have module id here so use the global id, 0.
    //
    RecyclableObject* obj = nullptr;
    if (!JavascriptConversion::ToObject(args[0], scriptContext, &obj))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("Object.prototype.__defineSetter__"));
    }

    Var setterFunc = args.Info.Count > 2 ? args[2] : obj->GetLibrary()->GetUndefined();

    if (!JavascriptConversion::IsCallable(setterFunc))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedFunction, _u("Object.prototype.__defineSetter__"));
    }

    Var propertyKey = args.Info.Count > 1 ? args[1] : obj->GetLibrary()->GetUndefined();
    const PropertyRecord* propertyRecord;
    JavascriptConversion::ToPropertyKey(propertyKey, scriptContext, &propertyRecord, nullptr);

    PropertyDescriptor propertyDescriptor;
    propertyDescriptor.SetEnumerable(true);
    propertyDescriptor.SetConfigurable(true);
    propertyDescriptor.SetSetter(setterFunc);

    DefineOwnPropertyHelper(obj, propertyRecord->GetPropertyId(), propertyDescriptor, scriptContext);

    return obj->GetLibrary()->GetUndefined();
}

// args[1] property name.
Var JavascriptObject::EntryLookupGetter(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    RecyclableObject* obj = nullptr;
    if (!JavascriptConversion::ToObject(args[0], scriptContext, &obj))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("Object.prototype.__lookupGetter__"));
    }

    Var propertyKey = args.Info.Count > 1 ? args[1] : obj->GetLibrary()->GetUndefined();
    const PropertyRecord* propertyRecord;
    JavascriptConversion::ToPropertyKey(propertyKey, scriptContext, &propertyRecord, nullptr);

    Var getter = nullptr;
    Var unused = nullptr;
    if (JavascriptOperators::GetAccessors(obj, propertyRecord->GetPropertyId(), scriptContext, &getter, &unused))
    {
        if (getter != nullptr)
        {
            return getter;
        }
    }

    return obj->GetLibrary()->GetUndefined();
}

// args[1] property name.
Var JavascriptObject::EntryLookupSetter(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    RecyclableObject* obj = nullptr;
    if (!JavascriptConversion::ToObject(args[0], scriptContext, &obj))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NullOrUndefined, _u("Object.prototype.__lookupSetter__"));
    }

    Var propertyKey = args.Info.Count > 1 ? args[1] : obj->GetLibrary()->GetUndefined();
    const PropertyRecord* propertyRecord;
    JavascriptConversion::ToPropertyKey(propertyKey, scriptContext, &propertyRecord, nullptr);

    Var unused = nullptr;
    Var setter = nullptr;
    if (JavascriptOperators::GetAccessors(obj, propertyRecord->GetPropertyId(), scriptContext, &unused, &setter))
    {
        if (setter != nullptr)
        {
            return setter;
        }
    }

    return obj->GetLibrary()->GetUndefined();
}

Var JavascriptObject::EntryIs(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    Var x = args.Info.Count > 1 ? args[1] : scriptContext->GetLibrary()->GetUndefined();
    Var y = args.Info.Count > 2 ? args[2] : scriptContext->GetLibrary()->GetUndefined();

    return JavascriptBoolean::ToVar(JavascriptConversion::SameValue(x, y), scriptContext);
}

//ES6 19.1.2.1
Var JavascriptObject::EntryAssign(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

    Assert(!(callInfo.Flags & CallFlags_New));

    // 1. Let to be ToObject(target).
    // 2. ReturnIfAbrupt(to).
    // 3  If only one argument was passed, return to.
    RecyclableObject* to = nullptr;
    if (args.Info.Count == 1 || !JavascriptConversion::ToObject(args[1], scriptContext, &to))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, _u("Object.assign"));
    }

    if (args.Info.Count < 3)
    {
        return to;
    }

    // 4. Let sources be the List of argument values starting with the second argument.
    // 5. For each element nextSource of sources, in ascending index order,
    CopyDataPropertiesHelper<true, true>(args[2], to, scriptContext);
    for (unsigned int i = 3; i < args.Info.Count; i++)
    {
        CopyDataPropertiesHelper<false, true>(args[i], to, scriptContext);
    }

    // 6. Return to.
    return to;
}

template <bool assign>
PCWSTR GetCopyDataPropertiesEntryName() {
    return assign ? _u("Object.assign") : _u("Object.Spread");
}

/*static*/
template <bool tryCopy, bool assign>
void JavascriptObject::CopyDataPropertiesHelper(Var source, RecyclableObject* to, ScriptContext* scriptContext, const BVSparse<Recycler>* excluded)
{
    // If assign is false, this performs CopyDataProperties(https://tc39.github.io/ecma262/#sec-copydataproperties).
    //    Variable excluded is ignored.
    // Otherwise, it performs the loop body of step 4 in Object.assign (https://tc39.github.io/ecma262/#sec-object.assign).
    //    Value of tryCopy is ignored.
    // 1. Assert Type(to) is Object.
    // 2. Assert Type(excluded) is List.
    // 3. If source is undefined or null, let keys be a new empty List
    // 4. Else,
    //    a. Let from be ToObject(source).
    //    b. Let keys be from.[[OwnpropertyKeys]]().
    // 5. Repeat for each element nextKey of keys in List order,
    //    a. If we are not in assign mode, then
    //        i. Let found be false.
    //        ii. Repeat for each element e of excluded,
    //            1. If e is not empty and SameValue(e, nextKey) is true, then
    //                a. Set found to true.
    //    b. If found is false or we are in assign mode, then
    //        i. Let desc be from.[[GetOwnProperty]](nextKey).
    //        ii. If desc is not undefined and desc.[[Enumerable]] is true, then
    //            1. Let propValue be Get(from, nextKey).
    //            2. If we are in assign mode, then
    //                a. Perform Set(to, nextKey, propValue, true).
    //            3. Else,
    //                a. Perform CreateDataProperty(to, nextKey, propValue).
    // 6. Return target (target is returned in pointer).
    Assert(JavascriptOperators::IsObject(to));
    RecyclableObject* from = nullptr;
    if (!JavascriptConversion::ToObject(source, scriptContext, &from))
    {
        if (JavascriptOperators::IsUndefinedOrNull(source))
        {
            return;
        }
        JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, GetCopyDataPropertiesEntryName<assign>());
    }

#if ENABLE_COPYONACCESS_ARRAY
    JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(from);
#endif
    // if proxy, take slow path by calling [[OwnPropertyKeys]] on source
    if (VarIs<JavascriptProxy>(from))
    {
        CopyDataPropertiesForProxyObjects<assign>(from, to, excluded, scriptContext);
    }
    // else use enumerator to extract keys from source
    else
    {
        if (assign)
        {
            bool copied = false;
            if (tryCopy)
            {
                DynamicObject* fromObj = DynamicObject::TryVarToBaseDynamicObject(from);
                DynamicObject* toObj = DynamicObject::TryVarToBaseDynamicObject(to);
                if (toObj && fromObj && toObj->GetType() == scriptContext->GetLibrary()->GetObjectType())
                {
                    copied = toObj->TryCopy(fromObj);
                }
            }
            if (!copied)
            {
                CopyDataPropertiesForGenericObjects<assign>(from, to, /*excluded*/ nullptr, scriptContext);
            }
        }
        else
        {
            CopyDataPropertiesForGenericObjects<assign>(from, to, excluded, scriptContext);
        }
    }
}

/*static*/
template <bool assign>
void JavascriptObject::CopyDataPropertiesForGenericObjects(RecyclableObject* from, RecyclableObject* to, const BVSparse<Recycler>* excluded, ScriptContext* scriptContext)
{
    // Steps 4 and 5 of CopyDataPropertiesHelper

    EnumeratorCache* cache = scriptContext->GetLibrary()->GetObjectAssignCache(from->GetType());
    JavascriptStaticEnumerator enumerator;
    if (!from->GetEnumerator(&enumerator, EnumeratorFlags::SnapShotSemantics | EnumeratorFlags::EnumSymbols | EnumeratorFlags::UseCache, scriptContext, cache))
    {
        // Nothing to enumerate, continue with the nextSource.
        return;
    }

    PropertyId nextKey = Constants::NoProperty;
    Var propValue = nullptr;
    JavascriptString * propertyName = nullptr;

    // Enumerate through each property of properties and fetch the property descriptor
    while ((propertyName = enumerator.MoveAndGetNext(nextKey)) != NULL)
    {
        // Make sure nextKey is defined for the current propertyName
        if (nextKey == Constants::NoProperty)
        {
            PropertyRecord const * propertyRecord = nullptr;

            scriptContext->GetOrAddPropertyRecord(propertyName, &propertyRecord);
            nextKey = propertyRecord->GetPropertyId();
        }

        bool found = false;
        if (excluded)
        {
            found = excluded->Test(nextKey);
        }
        if (!found)
        {
            PropertyString * propertyString = JavascriptOperators::TryFromVar<PropertyString>(propertyName);

            // If propertyName is a PropertyString* we can try getting the property from the inline cache to avoid having a full property lookup
            //
            // Whenever possible, our enumerator populates the cache, so we should generally get a cache hit here
            PropertyValueInfo getPropertyInfo;
            if (propertyString == nullptr || !propertyString->TryGetPropertyFromCache<true /* OwnPropertyOnly */, false /* OutputExistence */>(from, from, &propValue, scriptContext, &getPropertyInfo))
            {
                if (!JavascriptOperators::GetOwnProperty(from, nextKey, &propValue, scriptContext, &getPropertyInfo))
                {
                    JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, GetCopyDataPropertiesEntryName<assign>());
                }
            }

            if (assign)
            {
                // Similarly, try to set the property using our cache to avoid having to do the full work of SetProperty
                PropertyValueInfo setPropertyInfo;
                if (propertyString == nullptr || !propertyString->TrySetPropertyFromCache(to, propValue, scriptContext, PropertyOperation_ThrowIfNonWritable, &setPropertyInfo))
                {
                    if (!JavascriptOperators::SetProperty(to, to, nextKey, propValue, &setPropertyInfo, scriptContext, PropertyOperation_ThrowIfNonWritable))
                    {
                        JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, GetCopyDataPropertiesEntryName<assign>());
                    }
                }
            }
            else
            {
                //TODO[t-huyan]: potentially use caching to improve efficiency? (see AssignForGenericObjects implementation)
                if (!CreateDataProperty(to, nextKey, propValue, scriptContext))
                {
                    JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, GetCopyDataPropertiesEntryName<assign>());
                }
            }
        }
    }
}

/*static*/
template <bool assign>
void JavascriptObject::CopyDataPropertiesForProxyObjects(RecyclableObject* from, RecyclableObject* to, const BVSparse<Recycler>* excluded, ScriptContext* scriptContext)
{
    // Steps 4 and 5 of CopyDataPropertiesHelper
    JavascriptArray *keys = JavascriptOperators::GetOwnEnumerablePropertyNamesSymbols(from, scriptContext);
    uint32 length = keys->GetLength();
    Var nextKey;
    const PropertyRecord* propertyRecord = nullptr;
    PropertyId propertyId;
    Var propValue = nullptr;
    for (uint32 j = 0; j < length; j++)
    {
        PropertyDescriptor propertyDescriptor;
        nextKey = keys->DirectGetItem(j);
        AssertMsg(VarIs<JavascriptSymbol>(nextKey) || VarIs<JavascriptString>(nextKey), "Invariant check during ownKeys proxy trap should make sure we only get property key here. (symbol or string primitives)");
        // Spec doesn't strictly call for us to use ToPropertyKey but since we know nextKey is already a symbol or string primitive, ToPropertyKey will be a nop and return us the propertyRecord
        JavascriptConversion::ToPropertyKey(nextKey, scriptContext, &propertyRecord, nullptr);
        propertyId = propertyRecord->GetPropertyId();
        AssertMsg(propertyId != Constants::NoProperty, "CopyDataPropertiesForProxyObjects - OwnPropertyKeys returned a propertyId with value NoProperty.");

        bool found = false;
        if (excluded)
        {
            found = excluded->Test(propertyId);
        }
        if (!found)
        {
            if (JavascriptOperators::GetOwnPropertyDescriptor(from, propertyRecord->GetPropertyId(), scriptContext, &propertyDescriptor))
            {
                if (propertyDescriptor.IsEnumerable())
                {
                    if (!JavascriptOperators::GetOwnProperty(from, propertyId, &propValue, scriptContext, nullptr))
                    {
                        JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, GetCopyDataPropertiesEntryName<assign>());
                    }
                    if (assign)
                    {
                        if (!JavascriptOperators::SetProperty(to, to, propertyId, propValue, scriptContext))
                        {
                            JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, GetCopyDataPropertiesEntryName<assign>());
                        }
                    }
                    else
                    {
                        if (!CreateDataProperty(to, propertyId, propValue, scriptContext))
                        {
                            JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, GetCopyDataPropertiesEntryName<assign>());
                        }
                    }
                }
            }
        }
    }
}

BOOL JavascriptObject::CreateDataProperty(RecyclableObject* obj, PropertyId key, Var value, ScriptContext* scriptContext)
{
    // Performs CreateDataProperty operation as outlined in https://tc39.github.io/ecma262/#sec-createdataproperty.
    // 1. Assert: Type(obj) is Object
    // 2. Assert: IsPropertyKey(key) is true
    Assert(JavascriptOperators::IsObject(obj));

    // 3. Let newDesc be the PropertyDescriptor{[[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}.
    PropertyDescriptor newDesc;
    newDesc.SetValue(value);
    newDesc.SetWritable(true);
    newDesc.SetEnumerable(true);
    newDesc.SetConfigurable(true);

    // 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
    return DefineOwnPropertyHelper(obj, key, newDesc, scriptContext);
}

//ES5 15.2.3.5
Var JavascriptObject::EntryCreate(RecyclableObject* function, CallInfo callInfo, ...)
{
    PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();


    CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Object_Constructor_create)

    Assert(!(callInfo.Flags & CallFlags_New));

    if (args.Info.Count < 2)
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NotObjectOrNull, _u("Object.create"));
    }

    Var protoVar = args[1];
    if (!JavascriptOperators::IsObjectOrNull(protoVar))
    {
        JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NotObjectOrNull, _u("Object.create"));
    }

    RecyclableObject* protoObj = VarTo<RecyclableObject>(protoVar);
    DynamicObject* object = function->GetLibrary()->CreateObject(protoObj);

    JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_OBJECT(object));
#if ENABLE_DEBUG_CONFIG_OPTIONS
    if (Js::Configuration::Global.flags.IsEnabled(Js::autoProxyFlag))
    {
        object = VarTo<DynamicObject>(JavascriptProxy::AutoProxyWrapper(object));
    }
#endif

    if (args.Info.Count > 2 && JavascriptOperators::GetTypeId(args[2]) != TypeIds_Undefined)
    {
        RecyclableObject* properties = nullptr;
        if (FALSE == JavascriptConversion::ToObject(args[2], scriptContext, &properties))
        {
            JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NullOrUndefined, _u("Object.create"));
        }
        return DefinePropertiesHelper(object, properties, scriptContext);
    }
    return object;
}

Var JavascriptObject::DefinePropertiesHelper(RecyclableObject *object, RecyclableObject* props, ScriptContext *scriptContext)
{
    if (VarIs<JavascriptProxy>(props))
    {
        return DefinePropertiesHelperForProxyObjects(object, props, scriptContext);
    }
    else
    {
        return DefinePropertiesHelperForGenericObjects(object, props, scriptContext);
    }
}

Var JavascriptObject::DefinePropertiesHelperForGenericObjects(RecyclableObject *object, RecyclableObject* props, ScriptContext *scriptContext)
{
    size_t descSize = 16;
    size_t descCount = 0;
    struct DescriptorMap
    {
        Field(PropertyRecord const *) propRecord;
        Field(PropertyDescriptor) descriptor;
        Field(Var) originalVar;
    };

    JavascriptStaticEnumerator enumerator;
    if (!props->GetEnumerator(&enumerator, EnumeratorFlags::EnumSymbols, scriptContext))
    {
        return object;
    }

    ENTER_PINNED_SCOPE(DescriptorMap, descriptors);
    descriptors = RecyclerNewArray(scriptContext->GetRecycler(), DescriptorMap, descSize);

    PropertyId propId;
    PropertyRecord const * propertyRecord;
    JavascriptString* propertyName = nullptr;

    //enumerate through each property of properties and fetch the property descriptor
    while ((propertyName = enumerator.MoveAndGetNext(propId)) != NULL)
    {
        if (propId == Constants::NoProperty) //try current property id query first
        {
            scriptContext->GetOrAddPropertyRecord(propertyName, &propertyRecord);
            propId = propertyRecord->GetPropertyId();
        }
        else
        {
            propertyName->GetPropertyRecord(&propertyRecord);
        }

        if (descCount == descSize)
        {
            //reallocate - consider linked list of DescriptorMap if the descSize is too high
            descSize = AllocSizeMath::Mul(descCount, 2);
            __analysis_assume(descSize == descCount * 2);
            DescriptorMap *temp = RecyclerNewArray(scriptContext->GetRecycler(), DescriptorMap, descSize);

            for (size_t i = 0; i < descCount; i++)
            {
                temp[i] = descriptors[i];
            }
            descriptors = temp;
        }

        Var tempVar = JavascriptOperators::GetPropertyNoCache(props, propId, scriptContext);

        AnalysisAssert(descCount < descSize);
        if (!JavascriptOperators::ToPropertyDescriptor(tempVar, &descriptors[descCount].descriptor, scriptContext))
        {
            JavascriptError::ThrowTypeError(scriptContext, JSERR_PropertyDescriptor_Invalid, scriptContext->GetPropertyName(propId)->GetBuffer());
        }
        // In proxy, we need to get back the original ToPropertDescriptor var in [[defineProperty]] trap.
        descriptors[descCount].originalVar = tempVar;

        if (CONFIG_FLAG(UseFullName))
        {
            ModifyGetterSetterFuncName(propertyRecord, descriptors[descCount].descriptor, scriptContext);
        }

        descriptors[descCount].propRecord = propertyRecord;

        descCount++;
    }

    //Once all the property descriptors are in place set each property descriptor to the object
    for (size_t i = 0; i < descCount; i++)
    {
        DefineOwnPropertyHelper(object, descriptors[i].propRecord->GetPropertyId(), descriptors[i].descriptor, scriptContext);
    }

    LEAVE_PINNED_SCOPE();

    return object;
}

//ES5 15.2.3.7
Var JavascriptObject::DefinePropertiesHelperForProxyObjects(RecyclableObject *object, RecyclableObject* props, ScriptContext *scriptContext)
{
    Assert(VarIs<JavascriptProxy>(props));

    //1.  If Type(O) is not Object throw a TypeError exception.
    //2.  Let props be ToObject(Properties).

    size_t descCount = 0;
    struct DescriptorMap
    {
        Field(PropertyRecord const *) propRecord;
        Field(PropertyDescriptor) descriptor;
    };

    //3.  Let keys be props.[[OwnPropertyKeys]]().
    //4.  ReturnIfAbrupt(keys).
    //5.  Let descriptors be an empty List.
    JavascriptArray* keys = JavascriptOperators::GetOwnEnumerablePropertyNamesSymbols(props, scriptContext);
    uint32 length = keys->GetLength();

    ENTER_PINNED_SCOPE(DescriptorMap, descriptors);
    descriptors = RecyclerNewArray(scriptContext->GetRecycler(), DescriptorMap, length);

    //6.  Repeat for each element nextKey of keys in List order,
    //    1.  Let propDesc be props.[[GetOwnProperty]](nextKey).
    //    2.  ReturnIfAbrupt(propDesc).
    //    3.  If propDesc is not undefined and propDesc.[[Enumerable]] is true, then
    //        1.  Let descObj be Get(props, nextKey).
    //        2.  ReturnIfAbrupt(descObj).
    //        3.  Let desc be ToPropertyDescriptor(descObj).
    //        4.  ReturnIfAbrupt(desc).
    //        5.  Append the pair(a two element List) consisting of nextKey and desc to the end of descriptors.
    Var nextKey;

    const PropertyRecord* propertyRecord = nullptr;
    PropertyId propertyId;
    Var descObj;
    for (uint32 j = 0; j < length; j++)
    {
        PropertyDescriptor propertyDescriptor;
        nextKey = keys->DirectGetItem(j);
        AssertMsg(VarIs<JavascriptSymbol>(nextKey) || VarIs<JavascriptString>(nextKey), "Invariant check during ownKeys proxy trap should make sure we only get property key here. (symbol or string primitives)");
        JavascriptConversion::ToPropertyKey(nextKey, scriptContext, &propertyRecord, nullptr);
        propertyId = propertyRecord->GetPropertyId();
        AssertMsg(propertyId != Constants::NoProperty, "DefinePropertiesHelper - OwnPropertyKeys returned a propertyId with value NoProperty.");

        if (JavascriptOperators::GetOwnPropertyDescriptor(props, propertyRecord->GetPropertyId(), scriptContext, &propertyDescriptor))
        {
            if (propertyDescriptor.IsEnumerable())
            {
                descObj = JavascriptOperators::GetProperty(props, propertyId, scriptContext);

                if (!JavascriptOperators::ToPropertyDescriptor(descObj, &descriptors[descCount].descriptor, scriptContext))
                {
                    JavascriptError::ThrowTypeError(scriptContext, JSERR_PropertyDescriptor_Invalid, scriptContext->GetPropertyName(propertyId)->GetBuffer());
                }

                if (CONFIG_FLAG(UseFullName))
                {
                    ModifyGetterSetterFuncName(propertyRecord, descriptors[descCount].descriptor, scriptContext);
                }

                descriptors[descCount].propRecord = propertyRecord;

                descCount++;
            }
        }
    }

    //7.  For each pair from descriptors in list order,
    //    1.  Let P be the first element of pair.
    //    2.  Let desc be the second element of pair.
    //    3.  Let status be DefinePropertyOrThrow(O, P, desc).
    //    4.  ReturnIfAbrupt(status).

    for (size_t i = 0; i < descCount; i++)
    {
        DefineOwnPropertyHelper(object, descriptors[i].propRecord->GetPropertyId(), descriptors[i].descriptor, scriptContext);
    }

    LEAVE_PINNED_SCOPE();

    //8.  Return O.
    return object;
}

Var JavascriptObject::GetPrototypeOf(RecyclableObject* obj, ScriptContext* scriptContext)
{
    return obj->IsExternal() ? obj->GetConfigurablePrototype(scriptContext) : obj->GetPrototype();
}

//
// Check if "proto" is a prototype of "object" (on its prototype chain).
//
bool JavascriptObject::IsPrototypeOf(RecyclableObject* proto, RecyclableObject* object, ScriptContext* scriptContext)
{
    return JavascriptOperators::MapObjectAndPrototypesUntil<false>(object, [=](RecyclableObject* obj)
    {
        return obj == proto;
    });
}

bool JavascriptObject::IsPrototypeOfStopAtProxy(RecyclableObject* proto, RecyclableObject* object, ScriptContext* scriptContext)
{
    return JavascriptOperators::MapObjectAndPrototypesUntil<true>(object, [=](RecyclableObject* obj)
    {
        return obj == proto;
    });
}

static const size_t ConstructNameGetSetLength = 5;    // 5 = 1 ( for .) + 3 (get or set) + 1 for null)

/*static*/
char16 * JavascriptObject::ConstructName(const PropertyRecord * propertyRecord, const char16 * getOrSetStr, ScriptContext* scriptContext)
{
    Assert(propertyRecord);
    Assert(scriptContext);
    char16 * finalName = nullptr;
    size_t propertyLength = (size_t)propertyRecord->GetLength();
    if (propertyLength > 0)
    {
        size_t totalChars;
        if (SizeTAdd(propertyLength, ConstructNameGetSetLength, &totalChars) == S_OK)
        {
            finalName = RecyclerNewArrayLeafZ(scriptContext->GetRecycler(), char16, totalChars);
            Assert(finalName != nullptr);
            const char16* propertyName = propertyRecord->GetBuffer();
            Assert(propertyName != nullptr);
            wcscpy_s(finalName, totalChars, propertyName);

            Assert(getOrSetStr != nullptr);
            Assert(wcslen(getOrSetStr) == 4);

            wcscpy_s(finalName + propertyLength, ConstructNameGetSetLength, getOrSetStr);
        }
    }
    return finalName;
}

/*static*/
void JavascriptObject::ModifyGetterSetterFuncName(const PropertyRecord * propertyRecord, const PropertyDescriptor& descriptor, ScriptContext* scriptContext)
{
    Assert(scriptContext);
    Assert(propertyRecord);
    if (descriptor.GetterSpecified() || descriptor.SetterSpecified())
    {
        charcount_t propertyLength = propertyRecord->GetLength();

        if (descriptor.GetterSpecified()
            && Js::VarIs<Js::ScriptFunction>(descriptor.GetGetter())
            && _wcsicmp(Js::VarTo<Js::ScriptFunction>(descriptor.GetGetter())->GetFunctionProxy()->GetDisplayName(), _u("get")) == 0)
        {
            // modify to name.get
            const char16* finalName = ConstructName(propertyRecord, _u(".get"), scriptContext);
            if (finalName != nullptr)
            {
                FunctionProxy::SetDisplayNameFlags flags = (FunctionProxy::SetDisplayNameFlags) (FunctionProxy::SetDisplayNameFlagsDontCopy | FunctionProxy::SetDisplayNameFlagsRecyclerAllocated);

                Js::VarTo<Js::ScriptFunction>(descriptor.GetGetter())->GetFunctionProxy()->SetDisplayName(finalName,
                    propertyLength + 4 /*".get"*/, propertyLength + 1, flags);
            }
        }

        if (descriptor.SetterSpecified()
            && Js::VarIs<Js::ScriptFunction>(descriptor.GetSetter())
            && _wcsicmp(Js::VarTo<Js::ScriptFunction>(descriptor.GetSetter())->GetFunctionProxy()->GetDisplayName(), _u("set")) == 0)
        {
            // modify to name.set
            const char16* finalName = ConstructName(propertyRecord, _u(".set"), scriptContext);
            if (finalName != nullptr)
            {
                FunctionProxy::SetDisplayNameFlags flags = (FunctionProxy::SetDisplayNameFlags) (FunctionProxy::SetDisplayNameFlagsDontCopy | FunctionProxy::SetDisplayNameFlagsRecyclerAllocated);

                Js::VarTo<Js::ScriptFunction>(descriptor.GetSetter())->GetFunctionProxy()->SetDisplayName(finalName,
                    propertyLength + 4 /*".set"*/, propertyLength + 1, flags);
            }
        }
    }
}

BOOL JavascriptObject::DefineOwnPropertyHelper(RecyclableObject* obj, PropertyId propId, const PropertyDescriptor& descriptor, ScriptContext* scriptContext, bool throwOnError /* = true*/)
{
    BOOL returnValue;
    obj->ThrowIfCannotDefineProperty(propId, descriptor);

    const Type* oldType = obj->GetType();
    obj->ClearWritableDataOnlyDetectionBit();

    // HostDispatch: it doesn't support changing property attributes and default attributes are not per ES5,
    // so there is no benefit in using ES5 DefineOwnPropertyDescriptor for it, use old implementation.
    if (TypeIds_HostDispatch != obj->GetTypeId())
    {
        // for Array Exotic Objects
        if (DynamicObject::IsAnyArray(obj))
        {
            returnValue = JavascriptOperators::DefineOwnPropertyForArray(
                JavascriptArray::FromAnyArray(obj), propId, descriptor, throwOnError, scriptContext);
        }
        // for Integer Indexed Exotic Objects
        else if (DynamicObject::IsAnyTypedArray(obj))
        {
            returnValue = JavascriptOperators::DefineOwnPropertyForTypedArray(
                VarTo<TypedArrayBase>(obj), propId, descriptor, throwOnError, scriptContext);
        }
        // TODO: implement DefineOwnProperty for other object built-in exotic types.
        else
        {
            returnValue = JavascriptOperators::DefineOwnPropertyDescriptor(obj, propId, descriptor, throwOnError, scriptContext, Js::PropertyOperation_StrictMode);
            if (propId == PropertyIds::__proto__)
            {
                scriptContext->GetLibrary()->GetObjectPrototypeObject()->PostDefineOwnProperty__proto__(obj);
            }
        }
    }
    else
    {
        returnValue = JavascriptOperators::SetPropertyDescriptor(obj, propId, descriptor);
    }

    if (propId == PropertyIds::_symbolSpecies && obj == scriptContext->GetLibrary()->GetArrayConstructor())
    {
        scriptContext->GetLibrary()->SetArrayObjectHasUserDefinedSpecies(true);
    }

    if (obj->IsWritableDataOnlyDetectionBitSet())
    {
        if (obj->GetType() == oldType)
        {
            // Also, if the object's type has not changed, we need to ensure that
            // the cached property string for this property, if any, does not
            // specify this object's type.
            scriptContext->InvalidatePropertyStringAndSymbolCaches(propId, obj->GetType());
        }
    }

    if (descriptor.IsAccessorDescriptor())
    {
        scriptContext->optimizationOverrides.SetSideEffects(Js::SideEffects_Accessor);
    }
    return returnValue;
}

void JavascriptObject::SpreadObjectLiteral(Var source, Var to, ScriptContext* scriptContext)
{
    RecyclableObject* target = nullptr;
    bool succeeded = JavascriptConversion::ToObject(to, scriptContext, &target);
    AssertOrFailFast(succeeded);
    CopyDataPropertiesHelper<false, false>(source, target, scriptContext);
}

void JavascriptObject::Restify(Var source, Var to, void* excludedStatic, void* excludedComputed, ScriptContext* scriptContext)
{
    RecyclableObject* target = nullptr;
    bool succeeded = JavascriptConversion::ToObject(to, scriptContext, &target);
    AssertOrFailFast(succeeded);

    const Js::PropertyIdArray* propIdsStatic = reinterpret_cast<const Js::PropertyIdArray*>(excludedStatic);
    const Js::PropertyIdArray* propIdsComputed = reinterpret_cast<const Js::PropertyIdArray*>(excludedComputed);
    // Create a set of the property ids for faster lookup
    BVSparse<Recycler> excluded(scriptContext->GetRecycler());
    for (uint32 i = 0; i < propIdsStatic->count; i++)
    {
        PropertyId id = propIdsStatic->elements[i];
        excluded.Set(id);
    }
    // If these two are equal, this means there were no computed properties
    // and the static array was passed in to indicate this
    if (propIdsStatic != propIdsComputed)
    {
        for (uint32 i = 0; i < propIdsComputed->count; i++)
        {
            PropertyId id = propIdsComputed->elements[i];
            excluded.Set(id);
        }
    }

    CopyDataPropertiesHelper<false, false>(source, target, scriptContext, &excluded);
}
