blob: 0a92b64b1aea235be106fcf259d84c93eafef583 [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeLibraryPch.h"
namespace Js
{
FunctionInfo BoundFunction::functionInfo(FORCE_NO_WRITE_BARRIER_TAG(BoundFunction::NewInstance), FunctionInfo::DoNotProfile);
BoundFunction::BoundFunction(DynamicType * type)
: JavascriptFunction(type, &functionInfo),
targetFunction(nullptr),
boundThis(nullptr),
count(0),
boundArgs(nullptr)
{
// Constructor used during copy on write.
DebugOnly(VerifyEntryPoint());
}
BoundFunction::BoundFunction(Arguments args, DynamicType * type)
: JavascriptFunction(type, &functionInfo),
count(0),
boundArgs(nullptr)
{
DebugOnly(VerifyEntryPoint());
AssertMsg(args.Info.Count > 0, "wrong number of args in BoundFunction");
ScriptContext *scriptContext = this->GetScriptContext();
targetFunction = RecyclableObject::FromVar(args[0]);
// Let proto be targetFunction.[[GetPrototypeOf]]().
RecyclableObject* proto = JavascriptOperators::GetPrototype(targetFunction);
if (proto != type->GetPrototype())
{
if (type->GetIsShared())
{
this->ChangeType();
type = this->GetDynamicType();
}
type->SetPrototype(proto);
}
// If targetFunction is proxy, need to make sure that traps are called in right order as per 19.2.3.2 in RC#4 dated April 3rd 2015.
// Here although we won't use value of length, this is just to make sure that we call traps involved with HasOwnProperty(Target, "length") and Get(Target, "length")
if (JavascriptProxy::Is(targetFunction))
{
if (JavascriptOperators::HasOwnProperty(targetFunction, PropertyIds::length, scriptContext) == TRUE)
{
int len = 0;
Var varLength;
if (targetFunction->GetProperty(targetFunction, PropertyIds::length, &varLength, nullptr, scriptContext))
{
len = JavascriptConversion::ToInt32(varLength, scriptContext);
}
}
GetTypeHandler()->EnsureObjectReady(this);
}
if (args.Info.Count > 1)
{
boundThis = args[1];
// function object and "this" arg
const uint countAccountedFor = 2;
count = args.Info.Count - countAccountedFor;
// Store the args excluding function obj and "this" arg
if (args.Info.Count > 2)
{
boundArgs = RecyclerNewArray(scriptContext->GetRecycler(), Field(Var), count);
for (uint i=0; i<count; i++)
{
boundArgs[i] = args[i+countAccountedFor];
}
}
}
else
{
// If no "this" is passed, "undefined" is used
boundThis = scriptContext->GetLibrary()->GetUndefined();
}
}
BoundFunction::BoundFunction(RecyclableObject* targetFunction, Var boundThis, Var* args, uint argsCount, DynamicType * type)
: JavascriptFunction(type, &functionInfo),
count(argsCount),
boundArgs(nullptr)
{
DebugOnly(VerifyEntryPoint());
this->targetFunction = targetFunction;
this->boundThis = boundThis;
if (argsCount != 0)
{
this->boundArgs = RecyclerNewArray(this->GetScriptContext()->GetRecycler(), Field(Var), argsCount);
for (uint i = 0; i < argsCount; i++)
{
this->boundArgs[i] = args[i];
}
}
}
BoundFunction* BoundFunction::New(ScriptContext* scriptContext, ArgumentReader args)
{
Recycler* recycler = scriptContext->GetRecycler();
BoundFunction* boundFunc = RecyclerNew(recycler, BoundFunction, args,
scriptContext->GetLibrary()->GetBoundFunctionType());
return boundFunc;
}
Var BoundFunction::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
{
RUNTIME_ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
if (args.Info.Count == 0)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedFunction /* TODO-ERROR: get arg name - args[0] */);
}
BoundFunction *boundFunction = (BoundFunction *) function;
Var targetFunction = boundFunction->targetFunction;
//
// var o = new boundFunction()
// a new object should be created using the actual function object
//
Var newVarInstance = nullptr;
if (callInfo.Flags & CallFlags_New)
{
if (JavascriptProxy::Is(targetFunction))
{
JavascriptProxy* proxy = JavascriptProxy::FromVar(targetFunction);
Arguments proxyArgs(CallInfo(CallFlags_New, 1), &targetFunction);
args.Values[0] = newVarInstance = proxy->ConstructorTrap(proxyArgs, scriptContext, 0);
}
else
{
args.Values[0] = newVarInstance = JavascriptOperators::NewScObjectNoCtor(targetFunction, scriptContext);
}
}
Js::Arguments actualArgs = args;
if (boundFunction->count > 0)
{
BOOL isCrossSiteObject = boundFunction->IsCrossSiteObject();
// OACR thinks that this can change between here and the check in the for loop below
const unsigned int argCount = args.Info.Count;
if ((boundFunction->count + argCount) > CallInfo::kMaxCountArgs)
{
JavascriptError::ThrowRangeError(scriptContext, JSERR_ArgListTooLarge);
}
Field(Var) *newValues = RecyclerNewArray(scriptContext->GetRecycler(), Field(Var), boundFunction->count + argCount);
uint index = 0;
//
// For [[Construct]] use the newly created var instance
// For [[Call]] use the "this" to which bind bound it.
//
if (callInfo.Flags & CallFlags_New)
{
newValues[index++] = args[0];
}
else
{
newValues[index++] = boundFunction->boundThis;
}
// Copy the bound args
if (!isCrossSiteObject)
{
for (uint i = 0; i < boundFunction->count; i++)
{
newValues[index++] = boundFunction->boundArgs[i];
}
}
else
{
// it is possible that the bound arguments are not marshalled yet.
for (uint i = 0; i < boundFunction->count; i++)
{
//warning C6386: Buffer overrun while writing to 'newValues': the writable size is 'boundFunction->count+argCount*8' bytes, but '40' bytes might be written.
// there's throw with args.Info.Count == 0, so here won't hit buffer overrun, and __analyze_assume(argCount>0) does not work
#pragma warning(suppress: 6386)
newValues[index++] = CrossSite::MarshalVar(scriptContext, boundFunction->boundArgs[i]);
}
}
// Copy the extra args
for (uint i=1; i<argCount; i++)
{
newValues[index++] = args[i];
}
actualArgs = Arguments(args.Info, (Var*)newValues);
actualArgs.Info.Count = boundFunction->count + argCount;
}
else
{
if (!(callInfo.Flags & CallFlags_New))
{
actualArgs.Values[0] = boundFunction->boundThis;
}
}
RecyclableObject* actualFunction = RecyclableObject::FromVar(targetFunction);
Var aReturnValue = JavascriptFunction::CallFunction<true>(actualFunction, actualFunction->GetEntryPoint(), actualArgs);
//
// [[Construct]] and call returned a non-object
// return the newly created var instance
//
if ((callInfo.Flags & CallFlags_New) && !JavascriptOperators::IsObject(aReturnValue))
{
aReturnValue = newVarInstance;
}
return aReturnValue;
}
void BoundFunction::MarshalToScriptContext(Js::ScriptContext * scriptContext)
{
Assert(this->GetScriptContext() != scriptContext);
AssertMsg(VirtualTableInfo<BoundFunction>::HasVirtualTable(this), "Derived class need to define marshal to script context");
VirtualTableInfo<Js::CrossSiteObject<BoundFunction>>::SetVirtualTable(this);
this->targetFunction = (RecyclableObject*)CrossSite::MarshalVar(scriptContext, this->targetFunction);
this->boundThis = (RecyclableObject*)CrossSite::MarshalVar(this->GetScriptContext(), this->boundThis);
for (uint i = 0; i < count; i++)
{
this->boundArgs[i] = CrossSite::MarshalVar(this->GetScriptContext(), this->boundArgs[i]);
}
}
#if ENABLE_TTD
void BoundFunction::MarshalCrossSite_TTDInflate()
{
AssertMsg(VirtualTableInfo<BoundFunction>::HasVirtualTable(this), "Derived class need to define marshal");
VirtualTableInfo<Js::CrossSiteObject<BoundFunction>>::SetVirtualTable(this);
}
#endif
JavascriptFunction * BoundFunction::GetTargetFunction() const
{
if (targetFunction != nullptr)
{
RecyclableObject* _targetFunction = targetFunction;
while (JavascriptProxy::Is(_targetFunction))
{
_targetFunction = JavascriptProxy::FromVar(_targetFunction)->GetTarget();
}
if (JavascriptFunction::Is(_targetFunction))
{
return JavascriptFunction::FromVar(_targetFunction);
}
// targetFunction should always be a JavascriptFunction.
Assert(FALSE);
}
return nullptr;
}
JavascriptString* BoundFunction::GetDisplayNameImpl() const
{
JavascriptString* displayName = GetLibrary()->GetEmptyString();
if (targetFunction != nullptr)
{
Var value = JavascriptOperators::GetProperty(targetFunction, PropertyIds::name, targetFunction->GetScriptContext());
if (JavascriptString::Is(value))
{
displayName = JavascriptString::FromVar(value);
}
}
return LiteralString::Concat(LiteralString::NewCopySz(_u("bound "), this->GetScriptContext()), displayName);
}
RecyclableObject* BoundFunction::GetBoundThis()
{
if (boundThis != nullptr && RecyclableObject::Is(boundThis))
{
return RecyclableObject::FromVar(boundThis);
}
return NULL;
}
inline BOOL BoundFunction::IsConstructor() const
{
if (this->targetFunction != nullptr)
{
return JavascriptOperators::IsConstructor(this->GetTargetFunction());
}
return false;
}
PropertyQueryFlags BoundFunction::HasPropertyQuery(PropertyId propertyId)
{
if (propertyId == PropertyIds::length)
{
return Property_Found;
}
return JavascriptFunction::HasPropertyQuery(propertyId);
}
PropertyQueryFlags BoundFunction::GetPropertyQuery(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
BOOL result;
if (GetPropertyBuiltIns(originalInstance, propertyId, value, info, requestContext, &result))
{
return JavascriptConversion::BooleanToPropertyQueryFlags(result);
}
return JavascriptFunction::GetPropertyQuery(originalInstance, propertyId, value, info, requestContext);
}
PropertyQueryFlags BoundFunction::GetPropertyQuery(Var originalInstance, JavascriptString* propertyNameString, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
BOOL result;
PropertyRecord const* propertyRecord;
this->GetScriptContext()->FindPropertyRecord(propertyNameString, &propertyRecord);
if (propertyRecord != nullptr && GetPropertyBuiltIns(originalInstance, propertyRecord->GetPropertyId(), value, info, requestContext, &result))
{
return JavascriptConversion::BooleanToPropertyQueryFlags(result);
}
return JavascriptFunction::GetPropertyQuery(originalInstance, propertyNameString, value, info, requestContext);
}
bool BoundFunction::GetPropertyBuiltIns(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext, BOOL* result)
{
if (propertyId == PropertyIds::length)
{
// Get the "length" property of the underlying target function
int len = 0;
Var varLength;
if (targetFunction->GetProperty(targetFunction, PropertyIds::length, &varLength, nullptr, requestContext))
{
len = JavascriptConversion::ToInt32(varLength, requestContext);
}
// Reduce by number of bound args
len = len - this->count;
len = max(len, 0);
*value = JavascriptNumber::ToVar(len, requestContext);
*result = true;
return true;
}
return false;
}
PropertyQueryFlags BoundFunction::GetPropertyReferenceQuery(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext)
{
return BoundFunction::GetPropertyQuery(originalInstance, propertyId, value, info, requestContext);
}
BOOL BoundFunction::SetProperty(PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info)
{
BOOL result;
if (SetPropertyBuiltIns(propertyId, value, flags, info, &result))
{
return result;
}
return JavascriptFunction::SetProperty(propertyId, value, flags, info);
}
BOOL BoundFunction::SetProperty(JavascriptString* propertyNameString, Var value, PropertyOperationFlags flags, PropertyValueInfo* info)
{
BOOL result;
PropertyRecord const* propertyRecord;
this->GetScriptContext()->FindPropertyRecord(propertyNameString, &propertyRecord);
if (propertyRecord != nullptr && SetPropertyBuiltIns(propertyRecord->GetPropertyId(), value, flags, info, &result))
{
return result;
}
return JavascriptFunction::SetProperty(propertyNameString, value, flags, info);
}
bool BoundFunction::SetPropertyBuiltIns(PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info, BOOL* result)
{
if (propertyId == PropertyIds::length)
{
JavascriptError::ThrowCantAssignIfStrictMode(flags, this->GetScriptContext());
*result = false;
return true;
}
return false;
}
BOOL BoundFunction::GetAccessors(PropertyId propertyId, Var *getter, Var *setter, ScriptContext * requestContext)
{
return DynamicObject::GetAccessors(propertyId, getter, setter, requestContext);
}
DescriptorFlags BoundFunction::GetSetter(PropertyId propertyId, Var *setterValue, PropertyValueInfo* info, ScriptContext* requestContext)
{
return DynamicObject::GetSetter(propertyId, setterValue, info, requestContext);
}
DescriptorFlags BoundFunction::GetSetter(JavascriptString* propertyNameString, Var *setterValue, PropertyValueInfo* info, ScriptContext* requestContext)
{
return DynamicObject::GetSetter(propertyNameString, setterValue, info, requestContext);
}
BOOL BoundFunction::InitProperty(PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info)
{
return SetProperty(propertyId, value, PropertyOperation_None, info);
}
BOOL BoundFunction::DeleteProperty(PropertyId propertyId, PropertyOperationFlags flags)
{
if (propertyId == PropertyIds::length)
{
return false;
}
return JavascriptFunction::DeleteProperty(propertyId, flags);
}
BOOL BoundFunction::DeleteProperty(JavascriptString *propertyNameString, PropertyOperationFlags flags)
{
JsUtil::CharacterBuffer<WCHAR> propertyName(propertyNameString->GetString(), propertyNameString->GetLength());
if (BuiltInPropertyRecords::length.Equals(propertyName))
{
return false;
}
return JavascriptFunction::DeleteProperty(propertyNameString, flags);
}
BOOL BoundFunction::IsWritable(PropertyId propertyId)
{
if (propertyId == PropertyIds::length)
{
return false;
}
return JavascriptFunction::IsWritable(propertyId);
}
BOOL BoundFunction::IsConfigurable(PropertyId propertyId)
{
if (propertyId == PropertyIds::length)
{
return false;
}
return JavascriptFunction::IsConfigurable(propertyId);
}
BOOL BoundFunction::IsEnumerable(PropertyId propertyId)
{
if (propertyId == PropertyIds::length)
{
return false;
}
return JavascriptFunction::IsEnumerable(propertyId);
}
BOOL BoundFunction::HasInstance(Var instance, ScriptContext* scriptContext, IsInstInlineCache* inlineCache)
{
return this->targetFunction->HasInstance(instance, scriptContext, inlineCache);
}
#if ENABLE_TTD
void BoundFunction::MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor)
{
extractor->MarkVisitVar(this->targetFunction);
if(this->boundThis != nullptr)
{
extractor->MarkVisitVar(this->boundThis);
}
for(uint32 i = 0; i < this->count; ++i)
{
extractor->MarkVisitVar(this->boundArgs[i]);
}
}
void BoundFunction::ProcessCorePaths()
{
this->GetScriptContext()->TTDWellKnownInfo->EnqueueNewPathVarAsNeeded(this, this->targetFunction, _u("!targetFunction"));
this->GetScriptContext()->TTDWellKnownInfo->EnqueueNewPathVarAsNeeded(this, this->boundThis, _u("!boundThis"));
TTDAssert(this->count == 0, "Should only have empty args in core image");
}
TTD::NSSnapObjects::SnapObjectType BoundFunction::GetSnapTag_TTD() const
{
return TTD::NSSnapObjects::SnapObjectType::SnapBoundFunctionObject;
}
void BoundFunction::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc)
{
TTD::NSSnapObjects::SnapBoundFunctionInfo* bfi = alloc.SlabAllocateStruct<TTD::NSSnapObjects::SnapBoundFunctionInfo>();
bfi->TargetFunction = TTD_CONVERT_VAR_TO_PTR_ID(static_cast<RecyclableObject*>(this->targetFunction));
bfi->BoundThis = (this->boundThis != nullptr) ?
TTD_CONVERT_VAR_TO_PTR_ID(static_cast<Var>(this->boundThis)) : TTD_INVALID_PTR_ID;
bfi->ArgCount = this->count;
bfi->ArgArray = nullptr;
if(bfi->ArgCount > 0)
{
bfi->ArgArray = alloc.SlabAllocateArray<TTD::TTDVar>(bfi->ArgCount);
}
TTD_PTR_ID* depArray = alloc.SlabReserveArraySpace<TTD_PTR_ID>(bfi->ArgCount + 2 /*this and bound function*/);
depArray[0] = bfi->TargetFunction;
uint32 depCount = 1;
if(this->boundThis != nullptr && TTD::JsSupport::IsVarComplexKind(this->boundThis))
{
depArray[depCount] = bfi->BoundThis;
depCount++;
}
if(bfi->ArgCount > 0)
{
for(uint32 i = 0; i < bfi->ArgCount; ++i)
{
bfi->ArgArray[i] = this->boundArgs[i];
//Primitive kinds always inflated first so we only need to deal with complex kinds as depends on
if(TTD::JsSupport::IsVarComplexKind(this->boundArgs[i]))
{
depArray[depCount] = TTD_CONVERT_VAR_TO_PTR_ID(this->boundArgs[i]);
depCount++;
}
}
}
alloc.SlabCommitArraySpace<TTD_PTR_ID>(depCount, depCount + bfi->ArgCount);
TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<TTD::NSSnapObjects::SnapBoundFunctionInfo*, TTD::NSSnapObjects::SnapObjectType::SnapBoundFunctionObject>(objData, bfi, alloc, depCount, depArray);
}
BoundFunction* BoundFunction::InflateBoundFunction(ScriptContext* ctx, RecyclableObject* function, Var bThis, uint32 ct, Var* args)
{
BoundFunction* res = RecyclerNew(ctx->GetRecycler(), BoundFunction, ctx->GetLibrary()->GetBoundFunctionType());
res->boundThis = bThis;
res->count = ct;
res->boundArgs = (Field(Var)*)args;
res->targetFunction = function;
return res;
}
#endif
} // namespace Js