blob: 56642a3896a6719279639394c9f6ad61571fc397 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeLanguagePch.h"
#include "Types/PathTypeHandler.h"
#include "Types/PropertyIndexRanges.h"
#include "Types/UnscopablesWrapperObject.h"
#include "Types/SpreadArgument.h"
#include "Library/JavascriptPromise.h"
#include "Library/JavascriptRegularExpression.h"
#include "Library/ThrowErrorObject.h"
#include "Library/JavascriptGeneratorFunction.h"
#include "Library/ForInObjectEnumerator.h"
#include "Library/ES5Array.h"
#include "Types/SimpleDictionaryPropertyDescriptor.h"
#include "Types/SimpleDictionaryTypeHandler.h"
#include "Language/ModuleNamespace.h"
#ifndef SCRIPT_DIRECT_TYPE
typedef enum JsNativeValueType: int
{
JsInt8Type,
JsUint8Type,
JsInt16Type,
JsUint16Type,
JsInt32Type,
JsUint32Type,
JsInt64Type,
JsUint64Type,
JsFloatType,
JsDoubleType,
JsNativeStringType
} JsNativeValueType;
typedef struct JsNativeString
{
unsigned int length;
LPCWSTR str;
} JsNativeString;
#endif
using namespace Js;
DEFINE_RECYCLER_TRACKER_ARRAY_PERF_COUNTER(Var);
DEFINE_RECYCLER_TRACKER_PERF_COUNTER(FrameDisplay);
enum IndexType
{
IndexType_Number,
IndexType_PropertyId,
IndexType_JavascriptString
};
IndexType GetIndexTypeFromString(char16 const * propertyName, charcount_t propertyLength, ScriptContext* scriptContext, uint32* index, PropertyRecord const** propertyRecord, bool createIfNotFound)
{
if (JavascriptOperators::TryConvertToUInt32(propertyName, propertyLength, index) &&
(*index != JavascriptArray::InvalidIndex))
{
return IndexType_Number;
}
else
{
if (createIfNotFound)
{
scriptContext->GetOrAddPropertyRecord(propertyName, propertyLength, propertyRecord);
}
else
{
scriptContext->FindPropertyRecord(propertyName, propertyLength, propertyRecord);
}
return IndexType_PropertyId;
}
}
IndexType GetIndexTypeFromPrimitive(Var indexVar, ScriptContext* scriptContext, uint32* index, PropertyRecord const ** propertyRecord, JavascriptString ** propertyNameString, bool createIfNotFound, bool preferJavascriptStringOverPropertyRecord)
{
// CONSIDER: Only OP_SetElementI and OP_GetElementI use and take advantage of the
// IndexType_JavascriptString result. Consider modifying other callers of GetIndexType to take
// advantage of non-interned property strings where appropriate.
if (TaggedInt::Is(indexVar))
{
int indexInt = TaggedInt::ToInt32(indexVar);
if (indexInt >= 0)
{
*index = (uint)indexInt;
return IndexType_Number;
}
else
{
char16 stringBuffer[22];
int pos = TaggedInt::ToBuffer(indexInt, stringBuffer, _countof(stringBuffer));
charcount_t length = (_countof(stringBuffer) - 1) - pos;
if (createIfNotFound || preferJavascriptStringOverPropertyRecord)
{
// When preferring JavascriptString objects, just return a PropertyRecord instead
// of creating temporary JavascriptString objects for every negative integer that
// comes through here.
scriptContext->GetOrAddPropertyRecord(stringBuffer + pos, length, propertyRecord);
}
else
{
scriptContext->FindPropertyRecord(stringBuffer + pos, length, propertyRecord);
}
return IndexType_PropertyId;
}
}
if (JavascriptNumber::Is_NoTaggedIntCheck(indexVar))
{
// If this double can be a positive integer index, convert it.
int32 value = 0;
bool isInt32 = false;
if (JavascriptNumber::TryGetInt32OrUInt32Value(JavascriptNumber::GetValue(indexVar), &value, &isInt32)
&& !isInt32
&& static_cast<uint32>(value) < JavascriptArray::InvalidIndex)
{
*index = static_cast<uint32>(value);
return IndexType_Number;
}
// Fall through to slow string conversion.
}
JavascriptSymbol * symbol = JavascriptOperators::TryFromVar<JavascriptSymbol>(indexVar);
if (symbol)
{
// JavascriptSymbols cannot add a new PropertyRecord - they correspond to one and only one existing PropertyRecord.
// We already know what the PropertyRecord is since it is stored in the JavascriptSymbol itself so just return it.
*propertyRecord = symbol->GetValue();
return IndexType_PropertyId;
}
else
{
JavascriptString* indexStr = JavascriptConversion::ToString(indexVar, scriptContext);
char16 const * propertyName = indexStr->GetString();
charcount_t const propertyLength = indexStr->GetLength();
if (!createIfNotFound && preferJavascriptStringOverPropertyRecord)
{
if (JavascriptOperators::TryConvertToUInt32(propertyName, propertyLength, index) &&
(*index != JavascriptArray::InvalidIndex))
{
return IndexType_Number;
}
*propertyNameString = indexStr;
return IndexType_JavascriptString;
}
return GetIndexTypeFromString(propertyName, propertyLength, scriptContext, index, propertyRecord, createIfNotFound);
}
}
IndexType GetIndexTypeFromPrimitive(Var indexVar, ScriptContext* scriptContext, uint32* index, PropertyRecord const ** propertyRecord, bool createIfNotFound)
{
return GetIndexTypeFromPrimitive(indexVar, scriptContext, index, propertyRecord, nullptr, createIfNotFound, false);
}
IndexType GetIndexType(Var& indexVar, ScriptContext* scriptContext, uint32* index, PropertyRecord const ** propertyRecord, JavascriptString ** propertyNameString, bool createIfNotFound, bool preferJavascriptStringOverPropertyRecord)
{
indexVar = JavascriptConversion::ToPrimitive<JavascriptHint::HintString>(indexVar, scriptContext);
return GetIndexTypeFromPrimitive(indexVar, scriptContext, index, propertyRecord, propertyNameString, createIfNotFound, preferJavascriptStringOverPropertyRecord);
}
IndexType GetIndexType(Var& indexVar, ScriptContext* scriptContext, uint32* index, PropertyRecord const ** propertyRecord, bool createIfNotFound)
{
return GetIndexType(indexVar, scriptContext, index, propertyRecord, nullptr, createIfNotFound, false);
}
BOOL FEqualDbl(double dbl1, double dbl2)
{
// If the low ulongs don't match, they can't be equal.
if (Js::NumberUtilities::LuLoDbl(dbl1) != Js::NumberUtilities::LuLoDbl(dbl2))
return FALSE;
// If the high ulongs don't match, they can be equal iff one is -0 and
// the other is +0.
if (Js::NumberUtilities::LuHiDbl(dbl1) != Js::NumberUtilities::LuHiDbl(dbl2))
{
return 0x80000000 == (Js::NumberUtilities::LuHiDbl(dbl1) | Js::NumberUtilities::LuHiDbl(dbl2)) &&
0 == Js::NumberUtilities::LuLoDbl(dbl1);
}
// The bit patterns match. They are equal iff they are not Nan.
return !Js::NumberUtilities::IsNan(dbl1);
}
Var JavascriptOperators::OP_ApplyArgs(Var func, Var instance, __in_xcount(8) void** stackPtr, CallInfo callInfo, ScriptContext* scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_OP_ApplyArgs);
int argCount = callInfo.Count;
///
/// Check func has internal [[Call]] property
/// If not, throw TypeError
///
if (!JavascriptConversion::IsCallable(func)) {
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedFunction);
}
// Fix callInfo: expect result/value, and none of other flags are currently applicable.
// OP_ApplyArgs expects a result. Neither of {jit, interpreted} mode sends correct callFlags:
// LdArgCnt -- jit sends whatever was passed to current function, interpreter always sends 0.
// See Win8 bug 490489.
callInfo.Flags = CallFlags_Value;
RecyclableObject *funcPtr = UnsafeVarTo<RecyclableObject>(func);
PROBE_STACK(scriptContext, Js::Constants::MinStackDefault + argCount * 4);
JavascriptMethod entryPoint = funcPtr->GetEntryPoint();
Var ret;
switch (argCount) {
case 0:
Assert(false);
ret = CALL_ENTRYPOINT_NOASSERT(entryPoint, funcPtr, callInfo);
break;
case 1:
ret = CALL_ENTRYPOINT_NOASSERT(entryPoint, funcPtr, callInfo, instance);
break;
case 2:
ret = CALL_ENTRYPOINT_NOASSERT(entryPoint, funcPtr, callInfo, instance, stackPtr[0]);
break;
case 3:
ret = CALL_ENTRYPOINT_NOASSERT(entryPoint, funcPtr, callInfo, instance, stackPtr[0], stackPtr[1]);
break;
case 4:
ret = CALL_ENTRYPOINT_NOASSERT(entryPoint, funcPtr, callInfo, instance, stackPtr[0], stackPtr[1], stackPtr[2]);
break;
case 5:
ret = CALL_ENTRYPOINT_NOASSERT(entryPoint, funcPtr, callInfo, instance, stackPtr[0], stackPtr[1], stackPtr[2], stackPtr[3]);
break;
case 6:
ret = CALL_ENTRYPOINT_NOASSERT(entryPoint, funcPtr, callInfo, instance, stackPtr[0], stackPtr[1], stackPtr[2], stackPtr[3], stackPtr[4]);
break;
case 7:
ret = CALL_ENTRYPOINT_NOASSERT(entryPoint, funcPtr, callInfo, instance, stackPtr[0], stackPtr[1], stackPtr[2], stackPtr[3], stackPtr[4], stackPtr[5]);
break;
default:
{
// Don't need stack probe here- we just did so above
Arguments args(callInfo, stackPtr - 1);
BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext())
{
ret = JavascriptFunction::CallFunction<false>(funcPtr, entryPoint, args);
}
END_SAFE_REENTRANT_CALL
break;
}
}
return ret;
JIT_HELPER_END(Op_OP_ApplyArgs);
}
#ifdef _M_IX86
// Alias for overloaded JavascriptNumber::ToVar so it can be called unambiguously from native code
Var JavascriptOperators::Int32ToVar(int32 value, ScriptContext* scriptContext)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Op_Int32ToAtom);
return JavascriptNumber::ToVar(value, scriptContext);
JIT_HELPER_END(Op_Int32ToAtom);
}
// Alias for overloaded JavascriptNumber::ToVar so it can be called unambiguously from native code
Var JavascriptOperators::Int32ToVarInPlace(int32 value, ScriptContext* scriptContext, JavascriptNumber* result)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Op_Int32ToAtomInPlace);
return JavascriptNumber::ToVarInPlace(value, scriptContext, result);
JIT_HELPER_END(Op_Int32ToAtomInPlace);
}
// Alias for overloaded JavascriptNumber::ToVar so it can be called unambiguously from native code
Var JavascriptOperators::UInt32ToVar(uint32 value, ScriptContext* scriptContext)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Op_UInt32ToAtom);
return JavascriptNumber::ToVar(value, scriptContext);
JIT_HELPER_END(Op_UInt32ToAtom);
}
// Alias for overloaded JavascriptNumber::ToVar so it can be called unambiguously from native code
Var JavascriptOperators::UInt32ToVarInPlace(uint32 value, ScriptContext* scriptContext, JavascriptNumber* result)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Op_UInt32ToAtomInPlace);
return JavascriptNumber::ToVarInPlace(value, scriptContext, result);
JIT_HELPER_END(Op_UInt32ToAtomInPlace);
}
#endif
Var JavascriptOperators::OP_FinishOddDivBy2(uint32 value, ScriptContext *scriptContext)
{
return JavascriptNumber::New((double)(value + 0.5), scriptContext);
}
Var JavascriptOperators::ToNumberInPlace(Var aRight, ScriptContext* scriptContext, JavascriptNumber* result)
{
JIT_HELPER_REENTRANT_HEADER(Op_ConvNumberInPlace);
if (TaggedInt::Is(aRight) || JavascriptNumber::Is_NoTaggedIntCheck(aRight))
{
return aRight;
}
return JavascriptNumber::ToVarInPlace(JavascriptConversion::ToNumber(aRight, scriptContext), scriptContext, result);
JIT_HELPER_END(Op_ConvNumberInPlace);
}
Var JavascriptOperators::ToNumericInPlace(Var aRight, ScriptContext* scriptContext, JavascriptNumber* result)
{
if (JavascriptOperators::GetTypeId(aRight) == TypeIds_BigInt)
{
return aRight;
}
return JavascriptOperators::ToNumberInPlace(aRight, scriptContext, result);
}
Var JavascriptOperators::Typeof(Var var, ScriptContext* scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_Typeof);
switch (JavascriptOperators::GetTypeId(var))
{
case TypeIds_Undefined:
return scriptContext->GetLibrary()->GetUndefinedDisplayString();
case TypeIds_Null:
//null
return scriptContext->GetLibrary()->GetObjectTypeDisplayString();
case TypeIds_Integer:
case TypeIds_Number:
case TypeIds_Int64Number:
case TypeIds_UInt64Number:
return scriptContext->GetLibrary()->GetNumberTypeDisplayString();
default:
// Falsy objects are typeof 'undefined'.
if (VarTo<RecyclableObject>(var)->GetType()->IsFalsy())
{
return scriptContext->GetLibrary()->GetUndefinedDisplayString();
}
else
{
return VarTo<RecyclableObject>(var)->GetTypeOfString(scriptContext);
}
}
JIT_HELPER_END(Op_Typeof);
}
Var JavascriptOperators::TypeofFld(Var instance, PropertyId propertyId, ScriptContext* scriptContext)
{
return TypeofFld_Internal(instance, false, propertyId, scriptContext);
}
Var JavascriptOperators::TypeofRootFld(Var instance, PropertyId propertyId, ScriptContext* scriptContext)
{
return TypeofFld_Internal(instance, true, propertyId, scriptContext);
}
Var JavascriptOperators::TypeofFld_Internal(Var instance, const bool isRoot, PropertyId propertyId, ScriptContext* scriptContext)
{
RecyclableObject* object = nullptr;
if (FALSE == JavascriptOperators::GetPropertyObject(instance, scriptContext, &object))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_Property_CannotGet_NullOrUndefined , scriptContext->GetPropertyName(propertyId)->GetBuffer());
}
Var value = nullptr;
try
{
Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext);
// In edge mode, spec compat is more important than backward compat. Use spec/web behavior here
if (isRoot
? !JavascriptOperators::GetRootProperty(instance, propertyId, &value, scriptContext)
: !JavascriptOperators::GetProperty(instance, object, propertyId, &value, scriptContext))
{
return scriptContext->GetLibrary()->GetUndefinedDisplayString();
}
if (!scriptContext->IsUndeclBlockVar(value))
{
return JavascriptOperators::Typeof(value, scriptContext);
}
}
catch(const JavascriptException& err)
{
err.GetAndClear(); // discard exception object
return scriptContext->GetLibrary()->GetUndefinedDisplayString();
}
Assert(scriptContext->IsUndeclBlockVar(value));
JavascriptError::ThrowReferenceError(scriptContext, JSERR_UseBeforeDeclaration);
}
Var JavascriptOperators::TypeofElem_UInt32(Var instance, uint32 index, ScriptContext* scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_TypeofElem_UInt32);
if (JavascriptOperators::IsNumberFromNativeArray(instance, index, scriptContext))
return scriptContext->GetLibrary()->GetNumberTypeDisplayString();
#if FLOATVAR
return TypeofElem(instance, Js::JavascriptNumber::ToVar(index, scriptContext), scriptContext);
#else
char buffer[sizeof(Js::JavascriptNumber)];
return TypeofElem(instance, Js::JavascriptNumber::ToVarInPlace(index, scriptContext,
(Js::JavascriptNumber *)buffer), scriptContext);
#endif
JIT_HELPER_END(Op_TypeofElem_UInt32);
}
Var JavascriptOperators::TypeofElem_Int32(Var instance, int32 index, ScriptContext* scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_TypeofElem_Int32);
if (JavascriptOperators::IsNumberFromNativeArray(instance, index, scriptContext))
return scriptContext->GetLibrary()->GetNumberTypeDisplayString();
#if FLOATVAR
return TypeofElem(instance, Js::JavascriptNumber::ToVar(index, scriptContext), scriptContext);
#else
char buffer[sizeof(Js::JavascriptNumber)];
return TypeofElem(instance, Js::JavascriptNumber::ToVarInPlace(index, scriptContext,
(Js::JavascriptNumber *)buffer), scriptContext);
#endif
JIT_HELPER_END(Op_TypeofElem_Int32);
}
Js::JavascriptString* GetPropertyDisplayNameForError(Var prop, ScriptContext* scriptContext)
{
JavascriptString* str;
JavascriptSymbol *symbol = JavascriptOperators::TryFromVar<JavascriptSymbol>(prop);
if (symbol)
{
str = JavascriptSymbol::ToString(symbol->GetValue(), scriptContext);
}
else
{
str = JavascriptConversion::ToString(prop, scriptContext);
}
return str;
}
Var JavascriptOperators::TypeofElem(Var instance, Var index, ScriptContext* scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_TypeofElem);
RecyclableObject* object = nullptr;
if (FALSE == JavascriptOperators::GetPropertyObject(instance, scriptContext, &object))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_Property_CannotGet_NullOrUndefined, GetPropertyDisplayNameForError(index, scriptContext));
}
Var member = nullptr;
uint32 indexVal;
PropertyRecord const * propertyRecord = nullptr;
ThreadContext* threadContext = scriptContext->GetThreadContext();
ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags();
threadContext->ClearImplicitCallFlags();
try
{
Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext);
IndexType indexType = GetIndexType(index, scriptContext, &indexVal, &propertyRecord, false);
// For JS Objects, don't create the propertyId if not already added
if (indexType == IndexType_Number)
{
// In edge mode, we don't need to worry about the special "unknown" behavior. If the item is not available from Get,
// just return undefined.
if (!JavascriptOperators::GetItem(instance, object, indexVal, &member, scriptContext))
{
// If the instance doesn't have the item, typeof result is "undefined".
threadContext->CheckAndResetImplicitCallAccessorFlag();
threadContext->AddImplicitCallFlags(savedImplicitCallFlags);
return scriptContext->GetLibrary()->GetUndefinedDisplayString();
}
}
else
{
Assert(indexType == IndexType_PropertyId);
if (propertyRecord == nullptr && !JavascriptOperators::CanShortcutOnUnknownPropertyName(object))
{
indexType = GetIndexTypeFromPrimitive(index, scriptContext, &indexVal, &propertyRecord, true);
Assert(indexType == IndexType_PropertyId);
Assert(propertyRecord != nullptr);
}
if (propertyRecord != nullptr)
{
if (!JavascriptOperators::GetProperty(instance, object, propertyRecord->GetPropertyId(), &member, scriptContext))
{
// If the instance doesn't have the property, typeof result is "undefined".
threadContext->CheckAndResetImplicitCallAccessorFlag();
threadContext->AddImplicitCallFlags(savedImplicitCallFlags);
return scriptContext->GetLibrary()->GetUndefinedDisplayString();
}
}
else
{
#if DBG
JavascriptString* indexStr = JavascriptConversion::ToString(index, scriptContext);
PropertyRecord const * debugPropertyRecord;
scriptContext->GetOrAddPropertyRecord(indexStr, &debugPropertyRecord);
AssertMsg(!JavascriptOperators::GetProperty(instance, object, debugPropertyRecord->GetPropertyId(), &member, scriptContext), "how did this property come? See OS Bug 2727708 if you see this come from the web");
#endif
// If the instance doesn't have the property, typeof result is "undefined".
threadContext->CheckAndResetImplicitCallAccessorFlag();
threadContext->AddImplicitCallFlags(savedImplicitCallFlags);
return scriptContext->GetLibrary()->GetUndefinedDisplayString();
}
}
threadContext->CheckAndResetImplicitCallAccessorFlag();
threadContext->AddImplicitCallFlags(savedImplicitCallFlags);
return JavascriptOperators::Typeof(member, scriptContext);
}
catch(const JavascriptException& err)
{
err.GetAndClear(); // discard exception object
threadContext->CheckAndResetImplicitCallAccessorFlag();
threadContext->AddImplicitCallFlags(savedImplicitCallFlags);
return scriptContext->GetLibrary()->GetUndefinedDisplayString();
}
JIT_HELPER_END(Op_TypeofElem);
}
//
// Delete the given Var
//
Var JavascriptOperators::Delete(Var var, ScriptContext* scriptContext)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Op_Delete);
return scriptContext->GetLibrary()->GetTrue();
JIT_HELPER_END(Op_Delete);
}
BOOL JavascriptOperators::Equal_Full(Var aLeft, Var aRight, ScriptContext* requestContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_Equal_Full);
//
// Fast-path SmInts and paired Number combinations.
//
if (aLeft == aRight)
{
if (JavascriptNumber::Is(aLeft) && JavascriptNumber::IsNan(JavascriptNumber::GetValue(aLeft)))
{
return false;
}
else if (VarIs<JavascriptVariantDate>(aLeft) == false) // only need to check on aLeft - since they are the same var, aRight would do the same
{
return true;
}
else
{
//In ES5 mode strict equals (===) on same instance of object type VariantDate succeeds.
//Hence equals needs to succeed.
return true;
}
}
BOOL result = false;
if (TaggedInt::Is(aLeft))
{
if (TaggedInt::Is(aRight))
{
// If aLeft == aRight, we would already have returned true above.
return false;
}
else if (JavascriptNumber::Is_NoTaggedIntCheck(aRight))
{
return TaggedInt::ToDouble(aLeft) == JavascriptNumber::GetValue(aRight);
}
else
{
BOOL res = UnsafeVarTo<RecyclableObject>(aRight)->Equals(aLeft, &result, requestContext);
AssertMsg(res, "Should have handled this");
return result;
}
}
else if (JavascriptNumber::Is_NoTaggedIntCheck(aLeft))
{
if (TaggedInt::Is(aRight))
{
return TaggedInt::ToDouble(aRight) == JavascriptNumber::GetValue(aLeft);
}
else if(JavascriptNumber::Is_NoTaggedIntCheck(aRight))
{
return JavascriptNumber::GetValue(aLeft) == JavascriptNumber::GetValue(aRight);
}
else
{
BOOL res = UnsafeVarTo<RecyclableObject>(aRight)->Equals(aLeft, &result, requestContext);
AssertMsg(res, "Should have handled this");
return result;
}
}
if (UnsafeVarTo<RecyclableObject>(aLeft)->Equals(aRight, &result, requestContext))
{
return result;
}
else
{
return false;
}
JIT_HELPER_END(Op_Equal_Full);
}
BOOL JavascriptOperators::Greater_Full(Var aLeft,Var aRight,ScriptContext* scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_Greater_Full);
return RelationalComparisonHelper(aRight, aLeft, scriptContext, false, false);
JIT_HELPER_END(Op_Greater_Full);
}
BOOL JavascriptOperators::Less_Full(Var aLeft, Var aRight, ScriptContext* scriptContext)
{
return RelationalComparisonHelper(aLeft, aRight, scriptContext, true, false);
}
BOOL JavascriptOperators::RelationalComparisonHelper(Var aLeft, Var aRight, ScriptContext* scriptContext, bool leftFirst, bool undefinedAs)
{
TypeId typeId = JavascriptOperators::GetTypeId(aLeft);
if (typeId == TypeIds_Null)
{
aLeft=TaggedInt::ToVarUnchecked(0);
}
else if (typeId == TypeIds_Undefined)
{
aLeft=scriptContext->GetLibrary()->GetNaN();
}
typeId = JavascriptOperators::GetTypeId(aRight);
if (typeId == TypeIds_Null)
{
aRight=TaggedInt::ToVarUnchecked(0);
}
else if (typeId == TypeIds_Undefined)
{
aRight=scriptContext->GetLibrary()->GetNaN();
}
double dblLeft, dblRight;
TypeId leftType = JavascriptOperators::GetTypeId(aLeft);
TypeId rightType = JavascriptOperators::GetTypeId(aRight);
if ((leftType == TypeIds_BigInt) || (rightType == TypeIds_BigInt))
{
// TODO: support comparison with types other than BigInt
AssertOrFailFastMsg(leftType == rightType, "do not support comparison with types other than BigInt");
return JavascriptBigInt::LessThan(aLeft, aRight);
}
switch (leftType)
{
case TypeIds_Integer:
dblLeft = TaggedInt::ToDouble(aLeft);
switch (rightType)
{
case TypeIds_Integer:
dblRight = TaggedInt::ToDouble(aRight);
break;
case TypeIds_Number:
dblRight = JavascriptNumber::GetValue(aRight);
break;
default:
dblRight = JavascriptConversion::ToNumber(aRight, scriptContext);
break;
}
break;
case TypeIds_Number:
dblLeft = JavascriptNumber::GetValue(aLeft);
switch (rightType)
{
case TypeIds_Integer:
dblRight = TaggedInt::ToDouble(aRight);
break;
case TypeIds_Number:
dblRight = JavascriptNumber::GetValue(aRight);
break;
default:
dblRight = JavascriptConversion::ToNumber(aRight, scriptContext);
break;
}
break;
case TypeIds_Int64Number:
{
switch (rightType)
{
case TypeIds_Int64Number:
{
__int64 leftValue = UnsafeVarTo<JavascriptInt64Number>(aLeft)->GetValue();
__int64 rightValue = UnsafeVarTo<JavascriptInt64Number>(aRight)->GetValue();
return leftValue < rightValue;
}
break;
case TypeIds_UInt64Number:
{
__int64 leftValue = UnsafeVarTo<JavascriptInt64Number>(aLeft)->GetValue();
unsigned __int64 rightValue = UnsafeVarTo<JavascriptUInt64Number>(aRight)->GetValue();
if (rightValue <= INT_MAX && leftValue >= 0)
{
return leftValue < (__int64)rightValue;
}
}
break;
}
dblLeft = (double)UnsafeVarTo<JavascriptInt64Number>(aLeft)->GetValue();
dblRight = JavascriptConversion::ToNumber(aRight, scriptContext);
}
break;
// we cannot do double conversion between 2 int64 numbers as we can get wrong result after conversion
// i.e., two different numbers become the same after losing precision. We'll continue dbl comparison
// if either number is not an int64 number.
case TypeIds_UInt64Number:
{
switch (rightType)
{
case TypeIds_Int64Number:
{
unsigned __int64 leftValue = UnsafeVarTo<JavascriptUInt64Number>(aLeft)->GetValue();
__int64 rightValue = UnsafeVarTo<JavascriptInt64Number>(aRight)->GetValue();
if (leftValue < INT_MAX && rightValue >= 0)
{
return (__int64)leftValue < rightValue;
}
}
break;
case TypeIds_UInt64Number:
{
unsigned __int64 leftValue = UnsafeVarTo<JavascriptUInt64Number>(aLeft)->GetValue();
unsigned __int64 rightValue = UnsafeVarTo<JavascriptUInt64Number>(aRight)->GetValue();
return leftValue < rightValue;
}
break;
}
dblLeft = (double)UnsafeVarTo<JavascriptUInt64Number>(aLeft)->GetValue();
dblRight = JavascriptConversion::ToNumber(aRight, scriptContext);
}
break;
case TypeIds_String:
switch (rightType)
{
case TypeIds_Integer:
case TypeIds_Number:
case TypeIds_Boolean:
break;
default:
aRight = JavascriptConversion::ToPrimitive<JavascriptHint::HintNumber>(aRight, scriptContext);
rightType = JavascriptOperators::GetTypeId(aRight);
if (rightType != TypeIds_String)
{
dblRight = JavascriptConversion::ToNumber(aRight, scriptContext);
break;
}
case TypeIds_String:
return JavascriptString::LessThan(aLeft, aRight);
}
dblLeft = JavascriptConversion::ToNumber(aLeft, scriptContext);
dblRight = JavascriptConversion::ToNumber(aRight, scriptContext);
break;
case TypeIds_Boolean:
case TypeIds_Null:
case TypeIds_Undefined:
case TypeIds_Symbol:
dblLeft = JavascriptConversion::ToNumber(aLeft, scriptContext);
dblRight = JavascriptConversion::ToNumber(aRight, scriptContext);
break;
default:
if (leftFirst)
{
aLeft = JavascriptConversion::ToPrimitive<JavascriptHint::HintNumber>(aLeft, scriptContext);
aRight = JavascriptConversion::ToPrimitive<JavascriptHint::HintNumber>(aRight, scriptContext);
}
else
{
aRight = JavascriptConversion::ToPrimitive<JavascriptHint::HintNumber>(aRight, scriptContext);
aLeft = JavascriptConversion::ToPrimitive<JavascriptHint::HintNumber>(aLeft, scriptContext);
}
//BugFix: When @@ToPrimitive of an object is overridden with a function that returns null/undefined
//this helper will fall into a inescapable goto loop as the checks for null/undefined were outside of the path
return RelationalComparisonHelper(aLeft, aRight, scriptContext, leftFirst, undefinedAs);
}
//
// And +0,-0 that is not implemented fully
//
if (JavascriptNumber::IsNan(dblLeft) || JavascriptNumber::IsNan(dblRight))
{
return undefinedAs;
}
// this will succeed for -0.0 == 0.0 case as well
if (dblLeft == dblRight)
{
return false;
}
return dblLeft < dblRight;
}
BOOL JavascriptOperators::StrictEqualString(Var aLeft, JavascriptString* aRight)
{
JIT_HELPER_REENTRANT_HEADER(Op_StrictEqualString);
JIT_HELPER_SAME_ATTRIBUTES(Op_StrictEqualString, Op_StrictEqual);
JavascriptString* leftStr = TryFromVar<JavascriptString>(aLeft);
if (!leftStr)
{
return false;
}
JIT_HELPER_REENTRANT_HEADER(Op_StrictEqualString);
JIT_HELPER_SAME_ATTRIBUTES(Op_StrictEqualString, Op_StrictEqual);
return JavascriptString::Equals(leftStr, aRight);
JIT_HELPER_END(Op_StrictEqualString);
}
BOOL JavascriptOperators::StrictEqualEmptyString(Var aLeft)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Op_StrictEqualEmptyString);
JavascriptString * string = JavascriptOperators::TryFromVar<JavascriptString>(aLeft);
if (!string)
{
return false;
}
Assert(string);
return string->GetLength() == 0;
JIT_HELPER_END(Op_StrictEqualEmptyString);
}
#ifdef _CHAKRACOREBUILD
BOOL JavascriptOperators::StrictEqualNumberType(Var aLeft, Var aRight, TypeId leftType, TypeId rightType, ScriptContext *requestContext)
{
double dblLeft, dblRight;
switch (leftType)
{
case TypeIds_Integer:
switch (rightType)
{
case TypeIds_Integer:
return aLeft == aRight;
// we don't need to worry about int64: it cannot equal as we create
// JavascriptInt64Number only in overflow scenarios.
case TypeIds_Number:
dblLeft = TaggedInt::ToDouble(aLeft);
dblRight = JavascriptNumber::GetValue(aRight);
goto CommonNumber;
}
return FALSE;
case TypeIds_Int64Number:
switch (rightType)
{
case TypeIds_Int64Number:
{
__int64 leftValue = UnsafeVarTo<JavascriptInt64Number>(aLeft)->GetValue();
__int64 rightValue = UnsafeVarTo<JavascriptInt64Number>(aRight)->GetValue();
return leftValue == rightValue;
}
case TypeIds_UInt64Number:
{
__int64 leftValue = UnsafeVarTo<JavascriptInt64Number>(aLeft)->GetValue();
unsigned __int64 rightValue = VarTo<JavascriptUInt64Number>(aRight)->GetValue();
return ((unsigned __int64)leftValue == rightValue);
}
case TypeIds_Number:
dblLeft = (double)UnsafeVarTo<JavascriptInt64Number>(aLeft)->GetValue();
dblRight = JavascriptNumber::GetValue(aRight);
goto CommonNumber;
}
return FALSE;
case TypeIds_UInt64Number:
switch (rightType)
{
case TypeIds_Int64Number:
{
unsigned __int64 leftValue = UnsafeVarTo<JavascriptUInt64Number>(aLeft)->GetValue();
__int64 rightValue = UnsafeVarTo<JavascriptInt64Number>(aRight)->GetValue();
return (leftValue == (unsigned __int64)rightValue);
}
case TypeIds_UInt64Number:
{
unsigned __int64 leftValue = UnsafeVarTo<JavascriptUInt64Number>(aLeft)->GetValue();
unsigned __int64 rightValue = VarTo<JavascriptUInt64Number>(aRight)->GetValue();
return leftValue == rightValue;
}
case TypeIds_Number:
dblLeft = (double)UnsafeVarTo<JavascriptUInt64Number>(aLeft)->GetValue();
dblRight = JavascriptNumber::GetValue(aRight);
goto CommonNumber;
}
return FALSE;
case TypeIds_Number:
switch (rightType)
{
case TypeIds_Integer:
dblLeft = JavascriptNumber::GetValue(aLeft);
dblRight = TaggedInt::ToDouble(aRight);
goto CommonNumber;
case TypeIds_Int64Number:
dblLeft = JavascriptNumber::GetValue(aLeft);
dblRight = (double)VarTo<JavascriptInt64Number>(aRight)->GetValue();
goto CommonNumber;
case TypeIds_UInt64Number:
dblLeft = JavascriptNumber::GetValue(aLeft);
dblRight = (double)UnsafeVarTo<JavascriptUInt64Number>(aRight)->GetValue();
goto CommonNumber;
case TypeIds_Number:
dblLeft = JavascriptNumber::GetValue(aLeft);
dblRight = JavascriptNumber::GetValue(aRight);
CommonNumber:
return FEqualDbl(dblLeft, dblRight);
}
return FALSE;
}
Assert(0 && "Unreachable Code");
return FALSE;
}
BOOL JavascriptOperators::StrictEqual(Var aLeft, Var aRight, ScriptContext* requestContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_StrictEqual);
TypeId rightType, leftType;
leftType = JavascriptOperators::GetTypeId(aLeft);
// Because NaN !== NaN, we may not return TRUE when typeId is Number
if (aLeft == aRight && leftType != TypeIds_Number) return TRUE;
rightType = JavascriptOperators::GetTypeId(aRight);
if (leftType == TypeIds_String)
{
if (rightType == TypeIds_String)
{
return JavascriptString::Equals(UnsafeVarTo<JavascriptString>(aLeft), UnsafeVarTo<JavascriptString>(aRight));
}
return FALSE;
}
else if (leftType >= TypeIds_Integer && leftType <= TypeIds_UInt64Number)
{
return JavascriptOperators::StrictEqualNumberType(aLeft, aRight, leftType, rightType, requestContext);
}
else if (leftType == TypeIds_GlobalObject)
{
BOOL result;
if (UnsafeVarTo<RecyclableObject>(aLeft)->StrictEquals(aRight, &result, requestContext))
{
return result;
}
return false;
}
else if (leftType == TypeIds_BigInt)
{
if (rightType == TypeIds_BigInt)
{
return JavascriptBigInt::Equals(aLeft, aRight);
}
return FALSE;
}
return aLeft == aRight;
JIT_HELPER_END(Op_StrictEqual);
}
#else
BOOL JavascriptOperators::StrictEqual(Var aLeft, Var aRight, ScriptContext* requestContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_StrictEqual);
double dblLeft, dblRight;
TypeId rightType, leftType;
leftType = JavascriptOperators::GetTypeId(aLeft);
// Because NaN !== NaN, we may not return TRUE when typeId is Number
if (aLeft == aRight && leftType != TypeIds_Number) return TRUE;
rightType = JavascriptOperators::GetTypeId(aRight);
switch (leftType)
{
case TypeIds_String:
switch (rightType)
{
case TypeIds_String:
return JavascriptString::Equals(UnsafeVarTo<JavascriptString>(aLeft), UnsafeVarTo<JavascriptString>(aRight));
}
return FALSE;
case TypeIds_Integer:
switch (rightType)
{
case TypeIds_Integer:
return aLeft == aRight;
// we don't need to worry about int64: it cannot equal as we create
// JavascriptInt64Number only in overflow scenarios.
case TypeIds_Number:
dblLeft = TaggedInt::ToDouble(aLeft);
dblRight = JavascriptNumber::GetValue(aRight);
goto CommonNumber;
}
return FALSE;
case TypeIds_Int64Number:
switch (rightType)
{
case TypeIds_Int64Number:
{
__int64 leftValue = UnsafeVarTo<JavascriptInt64Number>(aLeft)->GetValue();
__int64 rightValue = UnsafeVarTo<JavascriptInt64Number>(aRight)->GetValue();
return leftValue == rightValue;
}
case TypeIds_UInt64Number:
{
__int64 leftValue = UnsafeVarTo<JavascriptInt64Number>(aLeft)->GetValue();
unsigned __int64 rightValue = VarTo<JavascriptUInt64Number>(aRight)->GetValue();
return ((unsigned __int64)leftValue == rightValue);
}
case TypeIds_Number:
dblLeft = (double)UnsafeVarTo<JavascriptInt64Number>(aLeft)->GetValue();
dblRight = JavascriptNumber::GetValue(aRight);
goto CommonNumber;
}
return FALSE;
case TypeIds_UInt64Number:
switch (rightType)
{
case TypeIds_Int64Number:
{
unsigned __int64 leftValue = UnsafeVarTo<JavascriptUInt64Number>(aLeft)->GetValue();
__int64 rightValue = UnsafeVarTo<JavascriptInt64Number>(aRight)->GetValue();
return (leftValue == (unsigned __int64)rightValue);
}
case TypeIds_UInt64Number:
{
unsigned __int64 leftValue = UnsafeVarTo<JavascriptUInt64Number>(aLeft)->GetValue();
unsigned __int64 rightValue = VarTo<JavascriptUInt64Number>(aRight)->GetValue();
return leftValue == rightValue;
}
case TypeIds_Number:
dblLeft = (double)UnsafeVarTo<JavascriptUInt64Number>(aLeft)->GetValue();
dblRight = JavascriptNumber::GetValue(aRight);
goto CommonNumber;
}
return FALSE;
case TypeIds_Number:
switch (rightType)
{
case TypeIds_Integer:
dblLeft = JavascriptNumber::GetValue(aLeft);
dblRight = TaggedInt::ToDouble(aRight);
goto CommonNumber;
case TypeIds_Int64Number:
dblLeft = JavascriptNumber::GetValue(aLeft);
dblRight = (double)VarTo<JavascriptInt64Number>(aRight)->GetValue();
goto CommonNumber;
case TypeIds_UInt64Number:
dblLeft = JavascriptNumber::GetValue(aLeft);
dblRight = (double)UnsafeVarTo<JavascriptUInt64Number>(aRight)->GetValue();
goto CommonNumber;
case TypeIds_Number:
dblLeft = JavascriptNumber::GetValue(aLeft);
dblRight = JavascriptNumber::GetValue(aRight);
CommonNumber:
return FEqualDbl(dblLeft, dblRight);
}
return FALSE;
case TypeIds_BigInt:
switch (rightType)
{
case TypeIds_BigInt:
return JavascriptBigInt::Equals(aLeft, aRight);
}
return FALSE;
case TypeIds_Boolean:
switch (rightType)
{
case TypeIds_Boolean:
return aLeft == aRight;
}
return FALSE;
case TypeIds_Undefined:
return rightType == TypeIds_Undefined;
case TypeIds_Null:
return rightType == TypeIds_Null;
case TypeIds_Array:
return (rightType == TypeIds_Array && aLeft == aRight);
#if DBG
case TypeIds_Symbol:
if (rightType == TypeIds_Symbol)
{
const PropertyRecord* leftValue = UnsafeVarTo<JavascriptSymbol>(aLeft)->GetValue();
const PropertyRecord* rightValue = UnsafeVarTo<JavascriptSymbol>(aRight)->GetValue();
Assert(leftValue != rightValue);
}
break;
#endif
case TypeIds_GlobalObject:
case TypeIds_HostDispatch:
switch (rightType)
{
case TypeIds_HostDispatch:
case TypeIds_GlobalObject:
{
BOOL result;
if(UnsafeVarTo<RecyclableObject>(aLeft)->StrictEquals(aRight, &result, requestContext))
{
return result;
}
return false;
}
}
break;
}
if (VarTo<RecyclableObject>(aLeft)->IsExternal())
{
BOOL result;
if (VarTo<RecyclableObject>(aLeft)->StrictEquals(aRight, &result, requestContext))
{
if (result)
{
return TRUE;
}
}
}
if (!TaggedNumber::Is(aRight) && VarTo<RecyclableObject>(aRight)->IsExternal())
{
BOOL result;
if (VarTo<RecyclableObject>(aRight)->StrictEquals(aLeft, &result, requestContext))
{
if (result)
{
return TRUE;
}
}
}
return aLeft == aRight;
JIT_HELPER_END(Op_StrictEqual);
}
#endif
BOOL JavascriptOperators::HasOwnProperty(
Var instance,
PropertyId propertyId,
_In_ ScriptContext* requestContext,
_In_opt_ PropertyString* propString)
{
if (TaggedNumber::Is(instance))
{
return FALSE;
}
RecyclableObject* object = UnsafeVarTo<RecyclableObject>(instance);
if (VarIs<JavascriptProxy>(instance))
{
PropertyDescriptor desc;
return GetOwnPropertyDescriptor(object, propertyId, requestContext, &desc);
}
// If we have a PropertyString, attempt to shortcut the lookup by using its caches
if (propString != nullptr)
{
PropertyCacheOperationInfo info;
if (propString->GetLdElemInlineCache()->PretendTryGetProperty(object->GetType(), &info))
{
switch (info.cacheType)
{
case CacheType_Local:
Assert(object->HasOwnProperty(propertyId));
return TRUE;
case CacheType_Proto:
Assert(!object->HasOwnProperty(propertyId));
return FALSE;
default:
// We had a cache hit, but cache doesn't tell us if we have an own property
break;
}
}
if (propString->GetStElemInlineCache()->PretendTrySetProperty(object->GetType(), object->GetType(), &info))
{
switch (info.cacheType)
{
case CacheType_Local:
Assert(object->HasOwnProperty(propertyId));
return TRUE;
case CacheType_LocalWithoutProperty:
Assert(!object->HasOwnProperty(propertyId));
return FALSE;
default:
// We had a cache hit, but cache doesn't tell us if we have an own property
break;
}
}
}
return object && object->HasOwnProperty(propertyId);
}
BOOL JavascriptOperators::GetOwnAccessors(Var instance, PropertyId propertyId, Var* getter, Var* setter, ScriptContext * requestContext)
{
BOOL result;
if (TaggedNumber::Is(instance))
{
result = false;
}
else
{
RecyclableObject* object = UnsafeVarTo<RecyclableObject>(instance);
result = object && object->GetAccessors(propertyId, getter, setter, requestContext);
}
return result;
}
JavascriptArray* JavascriptOperators::GetOwnPropertyNames(Var instance, ScriptContext *scriptContext)
{
RecyclableObject *object = ToObject(instance, scriptContext);
AssertOrFailFast(VarIsCorrectType(object)); // Consider moving this check into ToObject
JavascriptProxy * proxy = JavascriptOperators::TryFromVar<JavascriptProxy>(instance);
if (proxy)
{
return proxy->PropertyKeysTrap(JavascriptProxy::KeysTrapKind::GetOwnPropertyNamesKind, scriptContext);
}
return JavascriptObject::CreateOwnStringPropertiesHelper(object, scriptContext);
}
JavascriptArray* JavascriptOperators::GetOwnPropertySymbols(Var instance, ScriptContext *scriptContext)
{
RecyclableObject *object = ToObject(instance, scriptContext);
AssertOrFailFast(VarIsCorrectType(object));
CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Object_Constructor_getOwnPropertySymbols);
JavascriptProxy* proxy = JavascriptOperators::TryFromVar<JavascriptProxy>(instance);
if (proxy)
{
return proxy->PropertyKeysTrap(JavascriptProxy::KeysTrapKind::GetOwnPropertySymbolKind, scriptContext);
}
return JavascriptObject::CreateOwnSymbolPropertiesHelper(object, scriptContext);
}
JavascriptArray* JavascriptOperators::GetOwnPropertyKeys(Var instance, ScriptContext* scriptContext)
{
RecyclableObject *object = ToObject(instance, scriptContext);
AssertOrFailFast(VarIsCorrectType(object));
JavascriptProxy* proxy = JavascriptOperators::TryFromVar<JavascriptProxy>(instance);
if (proxy)
{
return proxy->PropertyKeysTrap(JavascriptProxy::KeysTrapKind::KeysKind, scriptContext);
}
return JavascriptObject::CreateOwnStringSymbolPropertiesHelper(object, scriptContext);
}
JavascriptArray* JavascriptOperators::GetOwnEnumerablePropertyNames(RecyclableObject* object, ScriptContext* scriptContext)
{
JavascriptProxy* proxy = JavascriptOperators::TryFromVar<JavascriptProxy>(object);
if (proxy)
{
JavascriptArray* proxyResult = proxy->PropertyKeysTrap(JavascriptProxy::KeysTrapKind::GetOwnPropertyNamesKind, scriptContext);
JavascriptArray* proxyResultToReturn = scriptContext->GetLibrary()->CreateArray(0);
// filter enumerable keys
uint32 resultLength = proxyResult->GetLength();
Var element;
const Js::PropertyRecord *propertyRecord = nullptr;
uint32 index = 0;
for (uint32 i = 0; i < resultLength; i++)
{
element = proxyResult->DirectGetItem(i);
Assert(!VarIs<JavascriptSymbol>(element));
PropertyDescriptor propertyDescriptor;
JavascriptConversion::ToPropertyKey(element, scriptContext, &propertyRecord, nullptr);
if (JavascriptOperators::GetOwnPropertyDescriptor(object, propertyRecord->GetPropertyId(), scriptContext, &propertyDescriptor))
{
if (propertyDescriptor.IsEnumerable())
{
proxyResultToReturn->DirectSetItemAt(index++, CrossSite::MarshalVar(scriptContext, element));
}
}
}
return proxyResultToReturn;
}
return JavascriptObject::CreateOwnEnumerableStringPropertiesHelper(object, scriptContext);
}
JavascriptArray* JavascriptOperators::GetOwnEnumerablePropertyNamesSymbols(RecyclableObject* object, ScriptContext* scriptContext)
{
JavascriptProxy* proxy = JavascriptOperators::TryFromVar<JavascriptProxy>(object);
if (proxy)
{
return proxy->PropertyKeysTrap(JavascriptProxy::KeysTrapKind::KeysKind, scriptContext);
}
return JavascriptObject::CreateOwnEnumerableStringSymbolPropertiesHelper(object, scriptContext);
}
BOOL JavascriptOperators::GetOwnProperty(Var instance, PropertyId propertyId, Var* value, ScriptContext* requestContext, PropertyValueInfo * propertyValueInfo)
{
BOOL result;
if (TaggedNumber::Is(instance))
{
result = false;
}
else
{
RecyclableObject* object = VarTo<RecyclableObject>(instance);
result = object && object->GetProperty(object, propertyId, value, propertyValueInfo, requestContext);
if (propertyValueInfo && result)
{
// We can only update the cache in case a property was found, because if it wasn't found, we don't know if it is missing or on a prototype
CacheOperators::CachePropertyRead(instance, object, false /* isRoot */, propertyId, false /* isMissing */, propertyValueInfo, requestContext);
}
}
return result;
}
BOOL JavascriptOperators::GetOwnPropertyDescriptor(RecyclableObject* obj, JavascriptString* propertyKey, ScriptContext* scriptContext, PropertyDescriptor* propertyDescriptor)
{
return JavascriptOperators::GetOwnPropertyDescriptor(obj, JavascriptOperators::GetPropertyId(propertyKey, scriptContext), scriptContext, propertyDescriptor);
}
// ES5's [[GetOwnProperty]].
// Return value:
// FALSE means "undefined" PD.
// TRUE means success. The propertyDescriptor parameter gets the descriptor.
//
BOOL JavascriptOperators::GetOwnPropertyDescriptor(RecyclableObject* obj, PropertyId propertyId, ScriptContext* scriptContext, PropertyDescriptor* propertyDescriptor)
{
Assert(obj);
Assert(scriptContext);
Assert(propertyDescriptor);
if (VarIs<JavascriptProxy>(obj))
{
return JavascriptProxy::GetOwnPropertyDescriptor(obj, propertyId, scriptContext, propertyDescriptor);
}
Var getter, setter;
if (false == JavascriptOperators::GetOwnAccessors(obj, propertyId, &getter, &setter, scriptContext))
{
Var value = nullptr;
if (false == JavascriptOperators::GetOwnProperty(obj, propertyId, &value, scriptContext, nullptr))
{
return FALSE;
}
if (nullptr != value)
{
propertyDescriptor->SetValue(value);
}
//CONSIDER : Its expensive to query for each flag from type system. Combine this with the GetOwnProperty to get all the flags
//at once. This will require a new API from type system and override in all the types which overrides IsEnumerable etc.
//Currently there is no performance tuning for ES5. This should be ok.
propertyDescriptor->SetWritable(FALSE != obj->IsWritable(propertyId));
}
else
{
if (nullptr == getter)
{
getter = scriptContext->GetLibrary()->GetUndefined();
}
propertyDescriptor->SetGetter(getter);
if (nullptr == setter)
{
setter = scriptContext->GetLibrary()->GetUndefined();
}
propertyDescriptor->SetSetter(setter);
}
propertyDescriptor->SetConfigurable(FALSE != obj->IsConfigurable(propertyId));
propertyDescriptor->SetEnumerable(FALSE != obj->IsEnumerable(propertyId));
return TRUE;
}
inline RecyclableObject* JavascriptOperators::GetPrototypeNoTrap(RecyclableObject* instance)
{
Type* type = instance->GetType();
if (type->HasSpecialPrototype())
{
if (type->GetTypeId() == TypeIds_Proxy)
{
// get back null
Assert(type->GetPrototype() == instance->GetScriptContext()->GetLibrary()->GetNull());
return type->GetPrototype();
}
else
{
return instance->GetPrototypeSpecial();
}
}
return type->GetPrototype();
}
BOOL JavascriptOperators::IsRemoteArray(RecyclableObject* instance)
{
TypeId remoteTypeId = TypeIds_Limit;
return (JavascriptOperators::GetRemoteTypeId(instance, &remoteTypeId) &&
DynamicObject::IsAnyArrayTypeId(remoteTypeId));
}
bool JavascriptOperators::IsArray(_In_ JavascriptProxy * instance)
{
// If it is a proxy, follow to the end of the proxy chain before checking if it is an array again.
JavascriptProxy * proxy = instance;
while (true)
{
RecyclableObject * targetInstance = proxy->GetTarget();
proxy = JavascriptOperators::TryFromVar<JavascriptProxy>(targetInstance);
if (proxy == nullptr)
{
return DynamicObject::IsAnyArray(targetInstance) || IsRemoteArray(targetInstance);
}
}
}
bool JavascriptOperators::IsArray(_In_ RecyclableObject* instance)
{
if (DynamicObject::IsAnyArray(instance))
{
return TRUE;
}
JavascriptProxy* proxy = JavascriptOperators::TryFromVar<JavascriptProxy>(instance);
if (proxy)
{
return IsArray(proxy);
}
return IsRemoteArray(instance);
}
bool JavascriptOperators::IsArray(_In_ Var instanceVar)
{
RecyclableObject* instanceObj = TryFromVar<RecyclableObject>(instanceVar);
return instanceObj && IsArray(instanceObj);
}
bool JavascriptOperators::IsConstructor(_In_ JavascriptProxy * instance)
{
// If it is a proxy, follow to the end of the proxy chain before checking if it is a constructor again.
JavascriptProxy * proxy = instance;
while (true)
{
RecyclableObject* targetInstance = proxy->GetTarget();
proxy = JavascriptOperators::TryFromVar<JavascriptProxy>(targetInstance);
if (proxy == nullptr)
{
JavascriptFunction* function = JavascriptOperators::TryFromVar<JavascriptFunction>(targetInstance);
return function && function->IsConstructor();
}
}
}
bool JavascriptOperators::IsConstructor(_In_ RecyclableObject* instanceObj)
{
JavascriptProxy* proxy = JavascriptOperators::TryFromVar<JavascriptProxy>(instanceObj);
if (proxy)
{
return IsConstructor(proxy);
}
JavascriptFunction* function = JavascriptOperators::TryFromVar<JavascriptFunction>(instanceObj);
return function && function->IsConstructor();
}
bool JavascriptOperators::IsConstructor(_In_ Var instanceVar)
{
RecyclableObject* instanceObj = TryFromVar<RecyclableObject>(instanceVar);
return instanceObj && IsConstructor(instanceObj);
}
BOOL JavascriptOperators::IsConcatSpreadable(Var instanceVar)
{
// an object is spreadable under two condition, either it is a JsArray
// or you define an isconcatSpreadable flag on it.
if (!JavascriptOperators::IsObject(instanceVar))
{
return false;
}
RecyclableObject* instance = UnsafeVarTo<RecyclableObject>(instanceVar);
ScriptContext* scriptContext = instance->GetScriptContext();
if (!PHASE_OFF1(IsConcatSpreadableCachePhase))
{
BOOL retVal = FALSE;
Type *instanceType = instance->GetType();
IsConcatSpreadableCache *isConcatSpreadableCache = scriptContext->GetThreadContext()->GetIsConcatSpreadableCache();
if (isConcatSpreadableCache->TryGetIsConcatSpreadable(instanceType, &retVal))
{
OUTPUT_TRACE(Phase::IsConcatSpreadableCachePhase, _u("IsConcatSpreadableCache hit: %p\n"), instanceType);
return retVal;
}
Var spreadable = nullptr;
BOOL hasUserDefinedSpreadable = JavascriptOperators::GetProperty(instance, instance, PropertyIds::_symbolIsConcatSpreadable, &spreadable, scriptContext);
if (hasUserDefinedSpreadable && spreadable != scriptContext->GetLibrary()->GetUndefined())
{
return JavascriptConversion::ToBoolean(spreadable, scriptContext);
}
retVal = JavascriptOperators::IsArray(instance);
if (!hasUserDefinedSpreadable)
{
OUTPUT_TRACE(Phase::IsConcatSpreadableCachePhase, _u("IsConcatSpreadableCache saved: %p\n"), instanceType);
isConcatSpreadableCache->CacheIsConcatSpreadable(instanceType, retVal);
}
return retVal;
}
Var spreadable = JavascriptOperators::GetProperty(instance, PropertyIds::_symbolIsConcatSpreadable, scriptContext);
if (spreadable != scriptContext->GetLibrary()->GetUndefined())
{
return JavascriptConversion::ToBoolean(spreadable, scriptContext);
}
return JavascriptOperators::IsArray(instance);
}
bool JavascriptOperators::IsConstructorSuperCall(Arguments args)
{
Var newTarget = args.GetNewTarget();
return args.IsNewCall() && newTarget != nullptr
&& !JavascriptOperators::IsUndefined(newTarget);
}
bool JavascriptOperators::GetAndAssertIsConstructorSuperCall(Arguments args)
{
bool isCtorSuperCall = JavascriptOperators::IsConstructorSuperCall(args);
Assert(isCtorSuperCall || !args.IsNewCall()
|| args[0] == nullptr || JavascriptOperators::GetTypeId(args[0]) == TypeIds_HostDispatch);
return isCtorSuperCall;
}
Var JavascriptOperators::OP_LdCustomSpreadIteratorList(Var aRight, ScriptContext* scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_ToSpreadedFunctionArgument);
#if ENABLE_COPYONACCESS_ARRAY
// We know we're going to read from this array. Do the conversion before we try to perform checks on the head segment.
JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray(aRight);
#endif
#ifdef ENABLE_JS_BUILTINS
scriptContext->GetLibrary()->EnsureBuiltInEngineIsReady();
#endif
RecyclableObject* function = GetIteratorFunction(aRight, scriptContext);
JavascriptMethod method = function->GetEntryPoint();
if (((JavascriptArray::IsNonES5Array(aRight) &&
(
JavascriptLibrary::IsDefaultArrayValuesFunction(function, scriptContext)
// Verify that the head segment of the array covers all elements with no gaps.
// Accessing an element on the prototype could have side-effects that would invalidate the optimization.
&& UnsafeVarTo<JavascriptArray>(aRight)->GetHead()->next == nullptr
&& UnsafeVarTo<JavascriptArray>(aRight)->GetHead()->left == 0
&& UnsafeVarTo<JavascriptArray>(aRight)->GetHead()->length == VarTo<JavascriptArray>(aRight)->GetLength()
&& UnsafeVarTo<JavascriptArray>(aRight)->HasNoMissingValues()
&& !UnsafeVarTo<JavascriptArray>(aRight)->IsCrossSiteObject()
)) ||
(VarIs<TypedArrayBase>(aRight) && method == TypedArrayBase::EntryInfo::Values.GetOriginalEntryPoint()))
// We can't optimize away the iterator if the array iterator prototype is user defined.
&& !JavascriptLibrary::ArrayIteratorPrototypeHasUserDefinedNext(scriptContext))
{
return RecyclerNew(scriptContext->GetRecycler(), SpreadArgument, aRight, true /*useDirectCall*/, scriptContext->GetLibrary()->GetSpreadArgumentType());
}
ThreadContext *threadContext = scriptContext->GetThreadContext();
Var iteratorVar =
threadContext->ExecuteImplicitCall(function, ImplicitCall_Accessor, [=]() -> Var
{
return CALL_FUNCTION(threadContext, function, CallInfo(Js::CallFlags_Value, 1), aRight);
});
if (!JavascriptOperators::IsObject(iteratorVar))
{
if (!threadContext->RecordImplicitException())
{
return scriptContext->GetLibrary()->GetUndefined();
}
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedObject);
}
return RecyclerNew(scriptContext->GetRecycler(), SpreadArgument, iteratorVar, false /*useDirectCall*/, scriptContext->GetLibrary()->GetSpreadArgumentType());
JIT_HELPER_END(Op_ToSpreadedFunctionArgument);
}
BOOL JavascriptOperators::IsPropertyUnscopable(Var instanceVar, JavascriptString *propertyString)
{
// This never gets called.
Throw::InternalError();
}
BOOL JavascriptOperators::IsPropertyUnscopable(Var instanceVar, PropertyId propertyId)
{
RecyclableObject* instance = VarTo<RecyclableObject>(instanceVar);
ScriptContext * scriptContext = instance->GetScriptContext();
Var unscopables = JavascriptOperators::GetProperty(instance, PropertyIds::_symbolUnscopables, scriptContext);
if (JavascriptOperators::IsObject(unscopables))
{
DynamicObject *unscopablesList = VarTo<DynamicObject>(unscopables);
Var value = nullptr;
//8.1.1.2.1.9.c If blocked is not undefined
if (JavascriptOperators::GetProperty(unscopablesList, propertyId, &value, scriptContext))
{
return JavascriptConversion::ToBoolean(value, scriptContext);
}
}
return false;
}
BOOL JavascriptOperators::HasProperty(RecyclableObject* instance, PropertyId propertyId)
{
while (!JavascriptOperators::IsNull(instance))
{
PropertyQueryFlags result = instance->HasPropertyQuery(propertyId, nullptr /*info*/);
if (result != PropertyQueryFlags::Property_NotFound)
{
return JavascriptConversion::PropertyQueryFlagsToBoolean(result); // return false if instance is typed array and HasPropertyQuery() returns PropertyQueryFlags::Property_Found_Undefined
}
instance = JavascriptOperators::GetPrototypeNoTrap(instance);
}
return false;
}
BOOL JavascriptOperators::HasPropertyUnscopables(RecyclableObject* instance, PropertyId propertyId)
{
return JavascriptOperators::HasProperty(instance, propertyId)
&& !IsPropertyUnscopable(instance, propertyId);
}
BOOL JavascriptOperators::HasRootProperty(RecyclableObject* instance, PropertyId propertyId)
{
Assert(VarIs<RootObjectBase>(instance));
RootObjectBase* rootObject = static_cast<RootObjectBase*>(instance);
if (rootObject->HasRootProperty(propertyId))
{
return true;
}
instance = instance->GetPrototype();
return HasProperty(instance, propertyId);
}
BOOL JavascriptOperators::HasProxyOrPrototypeInlineCacheProperty(RecyclableObject* instance, PropertyId propertyId)
{
TypeId typeId;
typeId = JavascriptOperators::GetTypeId(instance);
if (typeId == Js::TypeIds_Proxy)
{
// let's be more aggressive to disable inline prototype cache when proxy is presented in the prototypechain
return true;
}
do
{
instance = instance->GetPrototype();
typeId = JavascriptOperators::GetTypeId(instance);
if (typeId == Js::TypeIds_Proxy)
{
// let's be more aggressive to disable inline prototype cache when proxy is presented in the prototypechain
return true;
}
if (typeId == TypeIds_Null)
{
break;
}
/* We can rule out object with deferred type handler, because they would have expanded if they are in the cache */
if (!instance->HasDeferredTypeHandler() && instance->HasProperty(propertyId)) { return true; }
} while (typeId != TypeIds_Null);
return false;
}
BOOL JavascriptOperators::OP_HasProperty(Var instance, PropertyId propertyId, ScriptContext* scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_HasProperty);
RecyclableObject* object = TaggedNumber::Is(instance) ?
scriptContext->GetLibrary()->GetNumberPrototype() :
VarTo<RecyclableObject>(instance);
BOOL result = HasProperty(object, propertyId);
return result;
JIT_HELPER_END(Op_HasProperty);
}
BOOL JavascriptOperators::OP_HasOwnProperty(Var instance, PropertyId propertyId, ScriptContext* scriptContext, _In_opt_ PropertyString * propString)
{
RecyclableObject* object = TaggedNumber::Is(instance) ?
scriptContext->GetLibrary()->GetNumberPrototype() :
VarTo<RecyclableObject>(instance);
BOOL result = HasOwnProperty(object, propertyId, scriptContext, propString);
return result;
}
// CONSIDER: Have logic similar to HasOwnPropertyNoHostObjectForHeapEnum
BOOL JavascriptOperators::HasOwnPropertyNoHostObject(Var instance, PropertyId propertyId)
{
AssertMsg(!TaggedNumber::Is(instance), "HasOwnPropertyNoHostObject int passed");
RecyclableObject* object = VarTo<RecyclableObject>(instance);
return object && object->HasOwnPropertyNoHostObject(propertyId);
}
// CONSIDER: Remove HasOwnPropertyNoHostObjectForHeapEnum and use GetOwnPropertyNoHostObjectForHeapEnum in its place by changing it
// to return BOOL, true or false with whether the property exists or not, and return the value if not getter/setter as an out param.
BOOL JavascriptOperators::HasOwnPropertyNoHostObjectForHeapEnum(Var instance, PropertyId propertyId, ScriptContext* requestContext, Var& getter, Var& setter)
{
AssertMsg(!TaggedNumber::Is(instance), "HasOwnPropertyNoHostObjectForHeapEnum int passed");
RecyclableObject * object = VarTo<RecyclableObject>(instance);
if (StaticType::Is(object->GetTypeId()))
{
return FALSE;
}
getter = setter = NULL;
DynamicObject* dynamicObject = VarTo<DynamicObject>(instance);
Assert(dynamicObject->GetScriptContext()->IsHeapEnumInProgress());
if (dynamicObject->UseDynamicObjectForNoHostObjectAccess())
{
if (!dynamicObject->DynamicObject::GetAccessors(propertyId, &getter, &setter, requestContext))
{
Var value = nullptr;
if (!JavascriptConversion::PropertyQueryFlagsToBoolean(dynamicObject->DynamicObject::GetPropertyQuery(instance, propertyId, &value, NULL, requestContext)) ||
(requestContext->IsUndeclBlockVar(value) && (VarIs<ActivationObject>(instance) || VarIs<RootObjectBase>(instance))))
{
return FALSE;
}
}
}
else
{
if (!object->GetAccessors(propertyId, &getter, &setter, requestContext))
{
Var value = nullptr;
if (!object->GetProperty(instance, propertyId, &value, NULL, requestContext) ||
(requestContext->IsUndeclBlockVar(value) && (VarIs<ActivationObject>(instance) || VarIs<RootObjectBase>(instance))))
{
return FALSE;
}
}
}
return TRUE;
}
Var JavascriptOperators::GetOwnPropertyNoHostObjectForHeapEnum(Var instance, PropertyId propertyId, ScriptContext* requestContext, Var& getter, Var& setter)
{
AssertMsg(!TaggedNumber::Is(instance), "GetDataPropertyNoHostObject int passed");
Assert(HasOwnPropertyNoHostObjectForHeapEnum(instance, propertyId, requestContext, getter, setter) || getter || setter);
DynamicObject* dynamicObject = VarTo<DynamicObject>(instance);
getter = setter = NULL;
if (NULL == dynamicObject)
{
return requestContext->GetLibrary()->GetUndefined();
}
Var returnVar = requestContext->GetLibrary()->GetUndefined();
BOOL result = FALSE;
if (dynamicObject->UseDynamicObjectForNoHostObjectAccess())
{
if (! dynamicObject->DynamicObject::GetAccessors(propertyId, &getter, &setter, requestContext))
{
result = JavascriptConversion::PropertyQueryFlagsToBoolean((dynamicObject->DynamicObject::GetPropertyQuery(instance, propertyId, &returnVar, NULL, requestContext)));
}
}
else
{
if (! dynamicObject->GetAccessors(propertyId, &getter, &setter, requestContext))
{
result = dynamicObject->GetProperty(instance, propertyId, &returnVar, NULL, requestContext);
}
}
if (result)
{
return returnVar;
}
return requestContext->GetLibrary()->GetUndefined();
}
BOOL JavascriptOperators::OP_HasOwnPropScoped(Var scope, PropertyId propertyId, Var defaultInstance, ScriptContext* scriptContext)
{
AssertMsg(scope == scriptContext->GetLibrary()->GetNull() || JavascriptArray::IsNonES5Array(scope),
"Invalid scope chain pointer passed - should be null or an array");
JavascriptArray* arrScope = JavascriptArray::TryVarToNonES5Array(scope);
if (arrScope)
{
Var instance = arrScope->DirectGetItem(0);
return JavascriptOperators::OP_HasOwnProperty(instance, propertyId, scriptContext);
}
return JavascriptOperators::OP_HasOwnProperty(defaultInstance, propertyId, scriptContext);
}
BOOL JavascriptOperators::GetPropertyUnscopable(Var instance, RecyclableObject* propertyObject, PropertyId propertyId, Var* value, ScriptContext* requestContext, PropertyValueInfo* info)
{
return GetProperty_Internal<true>(instance, propertyObject, false, propertyId, value, requestContext, info);
}
BOOL JavascriptOperators::GetProperty(Var instance, RecyclableObject* propertyObject, PropertyId propertyId, Var* value, ScriptContext* requestContext, PropertyValueInfo* info)
{
return GetProperty_Internal<false>(instance, propertyObject, false, propertyId, value, requestContext, info);
}
BOOL JavascriptOperators::GetRootProperty(Var instance, PropertyId propertyId, Var* value, ScriptContext* requestContext, PropertyValueInfo* info)
{
return GetProperty_Internal<false>(instance, VarTo<RecyclableObject>(instance), true, propertyId, value, requestContext, info);
}
BOOL JavascriptOperators::GetProperty_InternalSimple(Var instance, RecyclableObject* object, PropertyId propertyId, _Outptr_result_maybenull_ Var* value, ScriptContext* requestContext)
{
BOOL foundProperty = FALSE;
Assert(value != nullptr);
while (!JavascriptOperators::IsNull(object))
{
PropertyQueryFlags result = object->GetPropertyQuery(instance, propertyId, value, nullptr, requestContext);
if (result != PropertyQueryFlags::Property_NotFound)
{
foundProperty = JavascriptConversion::PropertyQueryFlagsToBoolean(result);
break;
}
if (object->SkipsPrototype())
{
break;
}
object = JavascriptOperators::GetPrototypeNoTrap(object);
}
if (!foundProperty)
{
*value = requestContext->GetMissingPropertyResult();
}
return foundProperty;
}
template <bool unscopables>
BOOL JavascriptOperators::GetProperty_Internal(Var instance, RecyclableObject* propertyObject, const bool isRoot, PropertyId propertyId, Var* value, ScriptContext* requestContext, PropertyValueInfo* info)
{
if (TaggedNumber::Is(instance))
{
PropertyValueInfo::ClearCacheInfo(info);
}
RecyclableObject* object = propertyObject;
BOOL foundProperty = FALSE;
if (isRoot)
{
Assert(VarIs<RootObjectBase>(object));
RootObjectBase* rootObject = static_cast<RootObjectBase*>(object);
foundProperty = rootObject->GetRootProperty(instance, propertyId, value, info, requestContext);
}
while (!foundProperty && !JavascriptOperators::IsNull(object))
{
if (unscopables && IsPropertyUnscopable(object, propertyId))
{
break;
}
else
{
PropertyQueryFlags result = object->GetPropertyQuery(instance, propertyId, value, info, requestContext);
if (result != PropertyQueryFlags::Property_NotFound)
{
foundProperty = JavascriptConversion::PropertyQueryFlagsToBoolean(result);
break;
}
}
if (object->SkipsPrototype())
{
break;
}
object = JavascriptOperators::GetPrototypeNoTrap(object);
}
if (foundProperty)
{
#if ENABLE_FIXED_FIELDS && DBG
if (DynamicObject::IsBaseDynamicObject(object))
{
DynamicObject* dynamicObject = (DynamicObject*)object;
DynamicTypeHandler* dynamicTypeHandler = dynamicObject->GetDynamicType()->GetTypeHandler();
Var property;
if (dynamicTypeHandler->CheckFixedProperty(requestContext->GetPropertyName(propertyId), &property, requestContext))
{
bool skipAssert = false;
if (value != nullptr && Js::VarIs<Js::RecyclableObject>(property))
{
Js::RecyclableObject* pObject = Js::VarTo<Js::RecyclableObject>(property);
Js::RecyclableObject* pValue = Js::VarTo<Js::RecyclableObject>(*value);
if (pValue->GetScriptContext() != pObject->GetScriptContext())
{
// value was marshaled. skip check
skipAssert = true;
}
}
Assert(skipAssert || value == nullptr || *value == property);
}
}
#endif
// Don't cache the information if the value is undecl block var
// REVIEW: We might want to only check this if we need to (For LdRootFld or ScopedLdFld)
// Also we might want to throw here instead of checking it again in the caller
if (value && !requestContext->IsUndeclBlockVar(*value) && !VarIs<UnscopablesWrapperObject>(object))
{
CacheOperators::CachePropertyRead(propertyObject, object, isRoot, propertyId, false, info, requestContext);
}
#ifdef TELEMETRY_JSO
if (TELEMETRY_PROPERTY_OPCODE_FILTER(propertyId))
{
requestContext->GetTelemetry().GetOpcodeTelemetry().GetProperty(instance, propertyId, value, /*successful: */true);
}
#endif
return TRUE;
}
else
{
#ifdef MISSING_PROPERTY_STATS
if (PHASE_STATS1(MissingPropertyCachePhase))
{
requestContext->RecordMissingPropertyMiss();
}
#endif
if (PHASE_TRACE1(MissingPropertyCachePhase))
{
Output::Print(_u("MissingPropertyCaching: Missing property %d on slow path.\n"), propertyId);
}
TryCacheMissingProperty(instance, propertyObject, isRoot, propertyId, requestContext, info);
#if defined(TELEMETRY_JSO) || defined(TELEMETRY_AddToCache) // enabled for `TELEMETRY_AddToCache`, because this is the property-not-found codepath where the normal TELEMETRY_AddToCache code wouldn't be executed.
if (TELEMETRY_PROPERTY_OPCODE_FILTER(propertyId))
{
if (info && info->AllowResizingPolymorphicInlineCache()) // If in interpreted mode, not JIT.
{
requestContext->GetTelemetry().GetOpcodeTelemetry().GetProperty(instance, propertyId, nullptr);
}
}
#endif
*value = requestContext->GetMissingPropertyResult();
return FALSE;
}
}
// If the given instance is a type where we can cache missing properties, then cache that the given property ID is missing.
// cacheInstance is used as startingObject in CachePropertyRead, and might be instance's proto if we are fetching a super property (see #3064).
void JavascriptOperators::TryCacheMissingProperty(Var instance, Var cacheInstance, bool isRoot, PropertyId propertyId, ScriptContext* requestContext, _Inout_ PropertyValueInfo * info)
{
// Here, any well-behaved subclasses of DynamicObject can opt in to getting included in the missing property cache.
// For now, we only include basic objects and arrays.
if (PHASE_OFF1(MissingPropertyCachePhase) || isRoot || !(DynamicObject::IsBaseDynamicObject(instance) || DynamicObject::IsAnyArray(instance)))
{
return;
}
// CustomExternalObject in particular is problematic because in some cases it can report missing when implicit callsare disabled.
// See CustomExternalObject::GetPropertyQuery for an example.
if (UnsafeVarTo<DynamicObject>(instance)->GetType()->IsJsrtExternal() && requestContext->GetThreadContext()->IsDisableImplicitCall())
{
return;
}
DynamicTypeHandler* handler = UnsafeVarTo<DynamicObject>(instance)->GetDynamicType()->GetTypeHandler();
// Only cache missing property lookups for non-root field loads on objects that have PathTypeHandlers, because only these types have the right behavior
// when the missing property is later added. DictionaryTypeHandler's introduce the possibility that a stale TypePropertyCache entry with isMissing==true can
// be left in the cache after the property has been installed in the object's prototype chain. Other changes to optimize accesses to objects that don't
// override special symbols make it unnecessary to introduce an invalidation scheme to deal with DictionaryTypeHandler's.
if (!handler->IsPathTypeHandler())
{
return;
}
#ifdef MISSING_PROPERTY_STATS
if (PHASE_STATS1(MissingPropertyCachePhase))
{
requestContext->RecordMissingPropertyCacheAttempt();
}
#endif
if (PHASE_TRACE1(MissingPropertyCachePhase))
{
Output::Print(_u("MissingPropertyCache: Caching missing property for property %d.\n"), propertyId);
}
PropertyValueInfo::Set(info, requestContext->GetLibrary()->GetMissingPropertyHolder(), 0);
CacheOperators::CachePropertyRead(cacheInstance, requestContext->GetLibrary()->GetMissingPropertyHolder(), isRoot, propertyId, true /*isMissing*/, info, requestContext);
}
template<bool OutputExistence, typename PropertyKeyType> PropertyQueryFlags QueryGetOrHasProperty(
Var originalInstance, RecyclableObject* object, PropertyKeyType propertyKey, Var* value, PropertyValueInfo* info, ScriptContext* requestContext);
template<> PropertyQueryFlags QueryGetOrHasProperty<false /*OutputExistence*/, PropertyId /*PropertyKeyType*/>(
Var originalInstance, RecyclableObject* object, PropertyId propertyKey, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
return object->GetPropertyQuery(originalInstance, propertyKey, value, info, requestContext);
}
template<> PropertyQueryFlags QueryGetOrHasProperty<false /*OutputExistence*/, JavascriptString* /*PropertyKeyType*/>(
Var originalInstance, RecyclableObject* object, JavascriptString* propertyKey, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
return object->GetPropertyQuery(originalInstance, propertyKey, value, info, requestContext);
}
template<> PropertyQueryFlags QueryGetOrHasProperty<true /*OutputExistence*/, PropertyId /*PropertyKeyType*/>(
Var originalInstance, RecyclableObject* object, PropertyId propertyKey, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
PropertyQueryFlags result = object->HasPropertyQuery(propertyKey, info);
*value = JavascriptBoolean::ToVar(JavascriptConversion::PropertyQueryFlagsToBoolean(result), requestContext);
return result;
}
template<bool OutputExistence, typename PropertyKeyType>
BOOL JavascriptOperators::GetPropertyWPCache(Var instance, RecyclableObject* propertyObject, PropertyKeyType propertyKey, Var* value, ScriptContext* requestContext, _Inout_ PropertyValueInfo * info)
{
Assert(value);
RecyclableObject* object = propertyObject;
while (!JavascriptOperators::IsNull(object))
{
PropertyQueryFlags result = QueryGetOrHasProperty<OutputExistence>(instance, object, propertyKey, value, info, requestContext);
if (result != PropertyQueryFlags::Property_NotFound)
{
if (!VarIs<UnscopablesWrapperObject>(object) && info->GetPropertyRecordUsageCache())
{
PropertyId propertyId = info->GetPropertyRecordUsageCache()->GetPropertyRecord()->GetPropertyId();
CacheOperators::CachePropertyRead(instance, object, false, propertyId, false, info, requestContext);
}
return JavascriptConversion::PropertyQueryFlagsToBoolean(result);
}
// SkipsPrototype refers only to the Get operation, not Has. Some objects like CustomExternalObject respond
// to HasPropertyQuery with info only about the object itself and GetPropertyQuery with info about its prototype chain.
// For consistency with the behavior of JavascriptOperators::HasProperty, don't skip prototypes when outputting existence.
if (!OutputExistence && object->SkipsPrototype())
{
break;
}
object = JavascriptOperators::GetPrototypeNoTrap(object);
}
if (info->GetPropertyRecordUsageCache())
{
TryCacheMissingProperty(instance, instance, false /*isRoot*/, info->GetPropertyRecordUsageCache()->GetPropertyRecord()->GetPropertyId(), requestContext, info);
}
*value = OutputExistence
? requestContext->GetLibrary()->GetFalse()
: requestContext->GetMissingPropertyResult();
return FALSE;
}
bool JavascriptOperators::GetPropertyObjectForElementAccess(
_In_ Var instance,
_In_ Var index,
_In_ ScriptContext* scriptContext,
_Out_ RecyclableObject** propertyObject,
_In_ rtErrors error)
{
BOOL isNullOrUndefined = !GetPropertyObject(instance, scriptContext, propertyObject);
Assert(*propertyObject == instance || TaggedNumber::Is(instance));
if (isNullOrUndefined)
{
if (!scriptContext->GetThreadContext()->RecordImplicitException())
{
return false;
}
JavascriptError::ThrowTypeError(scriptContext, error, GetPropertyDisplayNameForError(index, scriptContext));
}
return true;
}
bool JavascriptOperators::GetPropertyObjectForSetElementI(
_In_ Var instance,
_In_ Var index,
_In_ ScriptContext* scriptContext,
_Out_ RecyclableObject** propertyObject)
{
return GetPropertyObjectForElementAccess(instance, index, scriptContext, propertyObject, JSERR_Property_CannotSet_NullOrUndefined);
}
bool JavascriptOperators::GetPropertyObjectForGetElementI(
_In_ Var instance,
_In_ Var index,
_In_ ScriptContext* scriptContext,
_Out_ RecyclableObject** propertyObject)
{
return GetPropertyObjectForElementAccess(instance, index, scriptContext, propertyObject, JSERR_Property_CannotGet_NullOrUndefined);
}
BOOL JavascriptOperators::GetPropertyObject(Var instance, ScriptContext * scriptContext, RecyclableObject** propertyObject)
{
Assert(propertyObject);
if (TaggedNumber::Is(instance))
{
*propertyObject = scriptContext->GetLibrary()->GetNumberPrototype();
return TRUE;
}
RecyclableObject* object = UnsafeVarTo<RecyclableObject>(instance);
*propertyObject = object;
if (JavascriptOperators::IsUndefinedOrNull(object))
{
return FALSE;
}
return TRUE;
}
#if DBG
BOOL JavascriptOperators::IsPropertyObject(RecyclableObject * instance)
{
TypeId typeId = JavascriptOperators::GetTypeId(instance);
return (typeId != TypeIds_Integer && typeId != TypeIds_Null && typeId != TypeIds_Undefined);
}
#endif
Var JavascriptOperators::OP_GetProperty(Var instance, PropertyId propertyId, ScriptContext* scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_GetProperty);
RecyclableObject* object = nullptr;
if (FALSE == JavascriptOperators::GetPropertyObject(instance, scriptContext, &object))
{
if (scriptContext->GetThreadContext()->RecordImplicitException())
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_Property_CannotGet_NullOrUndefined, scriptContext->GetPropertyName(propertyId)->GetBuffer());
}
else
{
return scriptContext->GetLibrary()->GetUndefined();
}
}
Var result = JavascriptOperators::GetPropertyNoCache(instance, object, propertyId, scriptContext);
AssertMsg(result != nullptr, "result null in OP_GetProperty");
return result;
JIT_HELPER_END(Op_GetProperty);
}
Var JavascriptOperators::OP_GetRootProperty(Var instance, PropertyId propertyId, PropertyValueInfo * info, ScriptContext* scriptContext)
{
AssertMsg(VarIs<RootObjectBase>(instance), "Root must be an object!");
Var value = nullptr;
if (JavascriptOperators::GetRootProperty(VarTo<RecyclableObject>(instance), propertyId, &value, scriptContext, info))
{
if (scriptContext->IsUndeclBlockVar(value) && scriptContext->GetThreadContext()->RecordImplicitException())
{
JavascriptError::ThrowReferenceError(scriptContext, JSERR_UseBeforeDeclaration);
}
return value;
}
const char16* propertyName = scriptContext->GetPropertyName(propertyId)->GetBuffer();
JavascriptFunction * caller = nullptr;
if (JavascriptStackWalker::GetCaller(&caller, scriptContext))
{
FunctionBody * callerBody = caller->GetFunctionBody();
if (callerBody && callerBody->GetUtf8SourceInfo()->GetIsXDomain())
{
propertyName = nullptr;
}
}
// Don't error if we disabled implicit calls
if (scriptContext->GetThreadContext()->RecordImplicitException())
{
JavascriptError::ThrowReferenceError(scriptContext, JSERR_UndefVariable, propertyName);
}
return scriptContext->GetMissingPropertyResult();
}
Var JavascriptOperators::OP_GetThisScoped(FrameDisplay *pScope, Var defaultInstance, ScriptContext* scriptContext)
{
// NOTE: If changes are made to this logic be sure to update the debuggers as well
int length = pScope->GetLength();
for (int i = 0; i < length; i += 1)
{
Var value = nullptr;
RecyclableObject *obj = VarTo<RecyclableObject>(pScope->GetItem(i));
if (JavascriptOperators::GetProperty(obj, Js::PropertyIds::_this, &value, scriptContext))
{
return value;
}
}
return defaultInstance;
}
Var JavascriptOperators::OP_UnwrapWithObj(Var aValue)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Op_UnwrapWithObj);
return VarTo<RecyclableObject>(aValue)->GetThisObjectOrUnWrap();
JIT_HELPER_END(Op_UnwrapWithObj);
}
Var JavascriptOperators::OP_GetInstanceScoped(FrameDisplay *pScope, PropertyId propertyId, Var rootObject, Var* thisVar, ScriptContext* scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_GetInstanceScoped);
// Similar to GetPropertyScoped, but instead of returning the property value, we return the instance that
// owns it, or the global object if no instance is found.
int i;
int length = pScope->GetLength();
for (i = 0; i < length; i++)
{
RecyclableObject *obj = (RecyclableObject*)pScope->GetItem(i);
if (JavascriptOperators::HasProperty(obj, propertyId))
{
// HasProperty will call UnscopablesWrapperObject's HasProperty which will do the filtering
// All we have to do here is unwrap the object hence the api call
*thisVar = obj->GetThisObjectOrUnWrap();
return *thisVar;
}
}
*thisVar = scriptContext->GetLibrary()->GetUndefined();
if (rootObject != scriptContext->GetGlobalObject())
{
if (JavascriptOperators::OP_HasProperty(rootObject, propertyId, scriptContext))
{
return rootObject;
}
}
return scriptContext->GetGlobalObject();
JIT_HELPER_END(Op_GetInstanceScoped);
}
Var JavascriptOperators::GetPropertyReference(RecyclableObject *instance, PropertyId propertyId, ScriptContext* requestContext)
{
Var value = nullptr;
PropertyValueInfo info;
if (JavascriptOperators::GetPropertyReference(instance, propertyId, &value, requestContext, &info))
{
Assert(value != nullptr);
return value;
}
return requestContext->GetMissingPropertyResult();
}
BOOL JavascriptOperators::GetPropertyReference(Var instance, RecyclableObject* propertyObject, PropertyId propertyId, Var* value, ScriptContext* requestContext, PropertyValueInfo* info)
{
return GetPropertyReference_Internal(instance, propertyObject, false, propertyId, value, requestContext, info);
}
BOOL JavascriptOperators::GetRootPropertyReference(RecyclableObject* instance, PropertyId propertyId, Var* value, ScriptContext* requestContext, PropertyValueInfo* info)
{
return GetPropertyReference_Internal(instance, instance, true, propertyId, value, requestContext, info);
}
BOOL JavascriptOperators::PropertyReferenceWalkUnscopable(Var instance, RecyclableObject** propertyObject, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
return PropertyReferenceWalk_Impl<true>(instance, propertyObject, propertyId, value, info, requestContext);
}
BOOL JavascriptOperators::PropertyReferenceWalk(Var instance, RecyclableObject** propertyObject, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
return PropertyReferenceWalk_Impl<false>(instance, propertyObject, propertyId, value, info, requestContext);
}
template <bool unscopables>
BOOL JavascriptOperators::PropertyReferenceWalk_Impl(Var instance, RecyclableObject** propertyObject, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
BOOL foundProperty = false;
RecyclableObject* object = *propertyObject;
while (!foundProperty && !JavascriptOperators::IsNull(object))
{
if (unscopables && JavascriptOperators::IsPropertyUnscopable(object, propertyId))
{
break;
}
else
{
PropertyQueryFlags result = object->GetPropertyReferenceQuery(instance, propertyId, value, info, requestContext);
if (result != PropertyQueryFlags::Property_NotFound)
{
foundProperty = JavascriptConversion::PropertyQueryFlagsToBoolean(result);
break;
}
}
if (object->SkipsPrototype())
{
break; // will return false
}
object = JavascriptOperators::GetPrototypeNoTrap(object);
}
*propertyObject = object;
return foundProperty;
}
BOOL JavascriptOperators::GetPropertyReference_Internal(Var instance, RecyclableObject* propertyObject, const bool isRoot, PropertyId propertyId, Var* value, ScriptContext* requestContext, PropertyValueInfo* info)
{
if (TaggedNumber::Is(instance))
{
PropertyValueInfo::ClearCacheInfo(info);
}
BOOL foundProperty = FALSE;
RecyclableObject* object = propertyObject;
if (isRoot)
{
foundProperty = VarTo<RootObjectBase>(object)->GetRootPropertyReference(instance, propertyId, value, info, requestContext);
}
if (!foundProperty)
{
foundProperty = PropertyReferenceWalk(instance, &object, propertyId, value, info, requestContext);
}
if (!foundProperty)
{
#if defined(TELEMETRY_JSO) || defined(TELEMETRY_AddToCache) // enabled for `TELEMETRY_AddToCache`, because this is the property-not-found codepath where the normal TELEMETRY_AddToCache code wouldn't be executed.
if (TELEMETRY_PROPERTY_OPCODE_FILTER(propertyId))
{
if (info && info->AllowResizingPolymorphicInlineCache()) // If in interpreted mode, not JIT.
{
requestContext->GetTelemetry().GetOpcodeTelemetry().GetProperty(instance, propertyId, nullptr);
}
}
#endif
*value = requestContext->GetMissingPropertyResult();
return foundProperty;
}
if (requestContext->IsUndeclBlockVar(*value))
{
JavascriptError::ThrowReferenceError(requestContext, JSERR_UseBeforeDeclaration);
}
#if ENABLE_FIXED_FIELDS && DBG
if (DynamicObject::IsBaseDynamicObject(object))
{
DynamicObject* dynamicObject = (DynamicObject*)object;
DynamicTypeHandler* dynamicTypeHandler = dynamicObject->GetDynamicType()->GetTypeHandler();
Var property = nullptr;
if (dynamicTypeHandler->CheckFixedProperty(requestContext->GetPropertyName(propertyId), &property, requestContext))
{
Assert(value == nullptr || *value == property);
}
}
#endif
CacheOperators::CachePropertyRead(instance, object, isRoot, propertyId, false, info, requestContext);
return TRUE;
}
template <typename PropertyKeyType, bool unscopable>
DescriptorFlags JavascriptOperators::GetterSetter_Impl(RecyclableObject* instance, PropertyKeyType propertyKey, Var* setterValue, PropertyValueInfo* info, ScriptContext* scriptContext)
{
DescriptorFlags flags = None;
RecyclableObject* object = instance;
while (flags == None && !JavascriptOperators::IsNull(object))
{
if (unscopable && IsPropertyUnscopable(object, propertyKey))
{
break;
}
else
{
flags = object->GetSetter(propertyKey, setterValue, info, scriptContext);
if (flags != None)
{
break;
}
}
// CONSIDER: we should add SkipsPrototype support. DOM has no ES 5 concepts built in that aren't
// already part of our prototype objects which are chakra objects.
object = object->GetPrototype();
}
return flags;
}
DescriptorFlags JavascriptOperators::GetterSetterUnscopable(RecyclableObject* instance, PropertyId propertyId, Var* setterValue, PropertyValueInfo* info, ScriptContext* scriptContext)
{
return GetterSetter_Impl<PropertyId, true>(instance, propertyId, setterValue, info, scriptContext);
}
DescriptorFlags JavascriptOperators::GetterSetter(RecyclableObject* instance, PropertyId propertyId, Var* setterValue, PropertyValueInfo* info, ScriptContext* scriptContext)
{
return GetterSetter_Impl<PropertyId, false>(instance, propertyId, setterValue, info, scriptContext);
}
DescriptorFlags JavascriptOperators::GetterSetter(RecyclableObject* instance, JavascriptString * propertyName, Var* setterValue, PropertyValueInfo* info, ScriptContext* scriptContext)
{
return GetterSetter_Impl<JavascriptString*, false>(instance, propertyName, setterValue, info, scriptContext);
}
void JavascriptOperators::OP_InvalidateProtoCaches(PropertyId propertyId, ScriptContext *scriptContext)
{
JIT_HELPER_NOT_REENTRANT_HEADER(InvalidateProtoCaches, reentrancylock, scriptContext->GetThreadContext());
scriptContext->InvalidateProtoCaches(propertyId);
JIT_HELPER_END(InvalidateProtoCaches);
}
// Checks to see if any object in the prototype chain has a property descriptor for the given index
// that specifies either an accessor or a non-writable attribute.
// If TRUE, check flags for details.
BOOL JavascriptOperators::CheckPrototypesForAccessorOrNonWritableItem(RecyclableObject* instance, uint32 index,
Var* setterValue, DescriptorFlags *flags, ScriptContext* scriptContext, BOOL skipPrototypeCheck /* = FALSE */)
{
Assert(setterValue);
Assert(flags);
// Do a quick walk up the prototype chain to see if any of the prototypes has ever had ANY setter or non-writable property.
if (CheckIfObjectAndPrototypeChainHasOnlyWritableDataProperties(instance))
{
return FALSE;
}
RecyclableObject* object = instance;
while (!JavascriptOperators::IsNull(object))
{
*flags = object->GetItemSetter(index, setterValue, scriptContext);
if (*flags != None || skipPrototypeCheck)
{
break;
}
object = object->GetPrototype();
}
return ((*flags & Accessor) == Accessor) || ((*flags & Proxy) == Proxy) || ((*flags & Data) == Data && (*flags & Writable) == None);
}
BOOL JavascriptOperators::SetGlobalPropertyNoHost(char16 const * propertyName, charcount_t propertyLength, Var value, ScriptContext * scriptContext)
{
GlobalObject * globalObject = scriptContext->GetGlobalObject();
uint32 index;
PropertyRecord const * propertyRecord = nullptr;
IndexType indexType = GetIndexTypeFromString(propertyName, propertyLength, scriptContext, &index, &propertyRecord, true);
if (indexType == IndexType_Number)
{
return globalObject->DynamicObject::SetItem(index, value, PropertyOperation_None);
}
return globalObject->DynamicObject::SetProperty(propertyRecord->GetPropertyId(), value, PropertyOperation_None, NULL);
}
template<typename PropertyKeyType>
BOOL JavascriptOperators::SetPropertyWPCache(Var receiver, RecyclableObject* object, PropertyKeyType propertyKey, Var newValue, ScriptContext* requestContext, PropertyOperationFlags propertyOperationFlags, _Inout_ PropertyValueInfo * info)
{
if (receiver)
{
AnalysisAssert(object);
Assert(!TaggedNumber::Is(receiver));
Var setterValueOrProxy = nullptr;
DescriptorFlags flags = None;
if (JavascriptOperators::CheckPrototypesForAccessorOrNonWritableProperty(object, propertyKey, &setterValueOrProxy, &flags, info, requestContext))
{
if ((flags & Accessor) == Accessor)
{