Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Promise.prototype.finally Fixes #3520 #4668

Merged
merged 1 commit into from
Feb 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/Runtime/Base/JnDirectFields.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ ENTRY2(false_, _u("false")) // "false" cannot be an identifier in C++ so using "
ENTRY(flags)
ENTRY(fill)
ENTRY(filter)
ENTRY(finally)
ENTRY(find)
ENTRY(findIndex)
ENTRY(fixed)
Expand Down
4 changes: 2 additions & 2 deletions lib/Runtime/ByteCode/ByteCodeCacheReleaseFileVersion.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
//-------------------------------------------------------------------------------------------------------
// NOTE: If there is a merge conflict the correct fix is to make a new GUID.

// {3A82B6DA-8211-48BD-AB78-A3A92520F7E3}
// {5E35A82C-3DF6-456D-807F-F87DFE3D43D0}
const GUID byteCodeCacheReleaseFileVersion =
{ 0x3A82B6DA, 0x8211, 0x48BD, { 0xAB, 0x78, 0xA3, 0xA9, 0x25, 0x20, 0xF7, 0xE3 } };
{ 0x5e35a82c, 0x3df6, 0x456d, { 0x80, 0x7f, 0xf8, 0x7d, 0xfe, 0x3d, 0x43, 0xd0 } };
1,914 changes: 957 additions & 957 deletions lib/Runtime/Library/InJavascript/Intl.js.bc.32b.h
100644 → 100755

Large diffs are not rendered by default.

1,922 changes: 961 additions & 961 deletions lib/Runtime/Library/InJavascript/Intl.js.bc.64b.h
100644 → 100755

Large diffs are not rendered by default.

1,906 changes: 953 additions & 953 deletions lib/Runtime/Library/InJavascript/Intl.js.nojit.bc.32b.h
100644 → 100755

Large diffs are not rendered by default.

1,906 changes: 953 additions & 953 deletions lib/Runtime/Library/InJavascript/Intl.js.nojit.bc.64b.h
100644 → 100755

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/Runtime/Library/JavascriptBuiltInFunctionList.h
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,7 @@ BUILTIN(JavascriptPromise, Race, EntryRace, FunctionInfo::ErrorOnNew)
BUILTIN(JavascriptPromise, Reject, EntryReject, FunctionInfo::ErrorOnNew)
BUILTIN(JavascriptPromise, Resolve, EntryResolve, FunctionInfo::ErrorOnNew)
BUILTIN(JavascriptPromise, Then, EntryThen, FunctionInfo::ErrorOnNew)
BUILTIN(JavascriptPromise, Finally, EntryFinally, FunctionInfo::ErrorOnNew)
BUILTIN(JavascriptPromise, Identity, EntryIdentityFunction, FunctionInfo::ErrorOnNew | FunctionInfo::DoNotProfile)
BUILTIN(JavascriptPromise, Thrower, EntryThrowerFunction, FunctionInfo::ErrorOnNew | FunctionInfo::DoNotProfile)
BUILTIN(JavascriptPromise, ResolveOrRejectFunction, EntryResolveOrRejectFunction, FunctionInfo::ErrorOnNew | FunctionInfo::DoNotProfile)
Expand Down
27 changes: 27 additions & 0 deletions lib/Runtime/Library/JavascriptLibrary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2446,6 +2446,10 @@ namespace Js
}
scriptContext->SetBuiltInLibraryFunction(JavascriptPromise::EntryInfo::Catch.GetOriginalEntryPoint(),
library->AddFunctionToLibraryObject(promisePrototype, PropertyIds::catch_, &JavascriptPromise::EntryInfo::Catch, 1));

scriptContext->SetBuiltInLibraryFunction(JavascriptPromise::EntryInfo::Finally.GetOriginalEntryPoint(),
library->AddFunctionToLibraryObject(promisePrototype, PropertyIds::finally, &JavascriptPromise::EntryInfo::Finally, 1));

library->AddMember(promisePrototype, PropertyIds::then, library->EnsurePromiseThenFunction(), PropertyBuiltInMethodDefaults);
scriptContext->SetBuiltInLibraryFunction(JavascriptPromise::EntryInfo::Then.GetOriginalEntryPoint(), library->EnsurePromiseThenFunction());

Expand Down Expand Up @@ -6956,6 +6960,29 @@ namespace Js
return function;
}

JavascriptPromiseThenFinallyFunction* JavascriptLibrary::CreatePromiseThenFinallyFunction(JavascriptMethod entryPoint, RecyclableObject* OnFinally, RecyclableObject* Constructor, bool shouldThrow)
{
Assert(scriptContext->GetConfig()->IsES6PromiseEnabled());

FunctionInfo* functionInfo = RecyclerNew(this->GetRecycler(), FunctionInfo, entryPoint);
DynamicType* type = DynamicType::New(scriptContext, TypeIds_Function, functionPrototype, entryPoint, GetDeferredAnonymousFunctionTypeHandler());

JavascriptPromiseThenFinallyFunction* function = RecyclerNewEnumClass(this->GetRecycler(), EnumFunctionClass, JavascriptPromiseThenFinallyFunction, type, functionInfo, OnFinally, Constructor, shouldThrow);
function->SetPropertyWithAttributes(PropertyIds::length, TaggedInt::ToVarUnchecked(1), PropertyConfigurable, nullptr);

return function;
}

JavascriptPromiseThunkFinallyFunction* JavascriptLibrary::CreatePromiseThunkFinallyFunction(JavascriptMethod entryPoint, Var value, bool shouldThrow)
{
Assert(scriptContext->GetConfig()->IsES6PromiseEnabled());

FunctionInfo* functionInfo = RecyclerNew(this->GetRecycler(), FunctionInfo, entryPoint);
DynamicType* type = CreateDeferredPrototypeFunctionType(entryPoint);

return RecyclerNewEnumClass(this->GetRecycler(), EnumFunctionClass, JavascriptPromiseThunkFinallyFunction, type, functionInfo, value, shouldThrow);
}

JavascriptExternalFunction* JavascriptLibrary::CreateWrappedExternalFunction(JavascriptExternalFunction* wrappedFunction)
{
// The wrapped function will have profiling, so the wrapper function does not need it.
Expand Down
2 changes: 2 additions & 0 deletions lib/Runtime/Library/JavascriptLibrary.h
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,8 @@ namespace Js
JavascriptPromiseReactionTaskFunction* CreatePromiseReactionTaskFunction(JavascriptMethod entryPoint, JavascriptPromiseReaction* reaction, Var argument);
JavascriptPromiseResolveThenableTaskFunction* CreatePromiseResolveThenableTaskFunction(JavascriptMethod entryPoint, JavascriptPromise* promise, RecyclableObject* thenable, RecyclableObject* thenFunction);
JavascriptPromiseAllResolveElementFunction* CreatePromiseAllResolveElementFunction(JavascriptMethod entryPoint, uint32 index, JavascriptArray* values, JavascriptPromiseCapability* capabilities, JavascriptPromiseAllResolveElementFunctionRemainingElementsWrapper* remainingElements);
JavascriptPromiseThenFinallyFunction* CreatePromiseThenFinallyFunction(JavascriptMethod entryPoint, RecyclableObject* OnFinally, RecyclableObject* Constructor, bool shouldThrow);
JavascriptPromiseThunkFinallyFunction* CreatePromiseThunkFinallyFunction(JavascriptMethod entryPoint, Var value, bool shouldThrow);
JavascriptExternalFunction* CreateWrappedExternalFunction(JavascriptExternalFunction* wrappedFunction);

#if ENABLE_NATIVE_CODEGEN
Expand Down
157 changes: 155 additions & 2 deletions lib/Runtime/Library/JavascriptPromise.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,159 @@ namespace Js
return CreateThenPromise(promise, fulfillmentHandler, rejectionHandler, scriptContext);
}

// Promise.prototype.finally as described in the draft ES 2018 #sec-promise.prototype.finally
Var JavascriptPromise::EntryFinally(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
Assert(!(callInfo.Flags & CallFlags_New));

ScriptContext* scriptContext = function->GetScriptContext();

AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("Promise.prototype.finally"));
// 1. Let promise be the this value
// 2. If Type(promise) is not Object, throw a TypeError exception
if (args.Info.Count < 1 || !JavascriptOperators::IsObject(args[0]))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedObject, _u("Promise.prototype.finally"));
}

JavascriptLibrary* library = scriptContext->GetLibrary();
RecyclableObject* promise = RecyclableObject::UnsafeFromVar(args[0]);
Copy link
Contributor

@dilijev dilijev Feb 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UnsafeFromVar(args[0]); [](start = 54, length = 23)

Is it okay to call UnsafeFromVar(args[0]) here? -- do we know for sure this has to be a RecyclableObject at this point? It is possible to call with this as, say, a number?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, because the check above covers that it is an object -- is it necessarily a RecyclableObject or could it be something else (I'm not sure the answer to this question)


In reply to: 167733902 [](ancestors = 167733902)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did a check above to make sure we have an args[0] and that it is an object. Should be guaranteed here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a type error thrown a couple of lines above if the arg is not an Object.

I'll also add a new test case that confirms that a type error is thrown if this is called with any non-promise object.

// 3. Let C be ? SpeciesConstructor(promise, %Promise%).
RecyclableObject* constructor = JavascriptOperators::SpeciesConstructor(promise, scriptContext->GetLibrary()->GetPromiseConstructor(), scriptContext);
// 4. Assert IsConstructor(C)
Assert(JavascriptOperators::IsConstructor(constructor));

// 5. If IsCallable(onFinally) is false
// a. Let thenFinally be onFinally
// b. Let catchFinally be onFinally
// 6. Else,
// a. Let thenFinally be a new built-in function object as defined in ThenFinally Function.
// b. Let catchFinally be a new built-in function object as defined in CatchFinally Function.
// c. Set thenFinally and catchFinally's [[Constructor]] internal slots to C.
// d. Set thenFinally and catchFinally's [[OnFinally]] internal slots to onFinally.

Var thenFinally = nullptr;
Var catchFinally = nullptr;

if (args.Info.Count > 1)
{
if (JavascriptConversion::IsCallable(args[1]))
{
//note to avoid duplicating code the ThenFinallyFunction works as both thenFinally and catchFinally using a flag
thenFinally = library->CreatePromiseThenFinallyFunction(EntryThenFinallyFunction, RecyclableObject::FromVar(args[1]), constructor, false);
catchFinally = library->CreatePromiseThenFinallyFunction(EntryThenFinallyFunction, RecyclableObject::FromVar(args[1]), constructor, true);
}
else
{
thenFinally = args[1];
catchFinally = args[1];
}
}
else
{
thenFinally = library->GetUndefined();
catchFinally = library->GetUndefined();
}

Assert(thenFinally != nullptr && catchFinally != nullptr);

// 7. Return ? Invoke(promise, "then", << thenFinally, catchFinally >>).
Var funcVar = JavascriptOperators::GetProperty(promise, Js::PropertyIds::then, scriptContext);
if (!JavascriptConversion::IsCallable(funcVar))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedFunction, _u("Promise.prototype.finally"));
}
RecyclableObject* func = RecyclableObject::UnsafeFromVar(funcVar);

return CALL_FUNCTION(scriptContext->GetThreadContext(),
func, Js::CallInfo(CallFlags_Value, 3),
promise,
thenFinally,
catchFinally);
}

// ThenFinallyFunction as described in draft ES2018 #sec-thenfinallyfunctions
// AND CatchFinallyFunction as described in draft ES2018 #sec-catchfinallyfunctions
Var JavascriptPromise::EntryThenFinallyFunction(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
Assert(!(callInfo.Flags & CallFlags_New));
ScriptContext* scriptContext = function->GetScriptContext();

JavascriptLibrary* library = scriptContext->GetLibrary();

JavascriptPromiseThenFinallyFunction* thenFinallyFunction = JavascriptPromiseThenFinallyFunction::FromVar(function);

// 1. Let onFinally be F.[[OnFinally]]
// 2. Assert: IsCallable(onFinally)=true
Assert(JavascriptConversion::IsCallable(thenFinallyFunction->GetOnFinally()));

// 3. Let result be ? Call(onFinally, undefined)
Var result = CALL_FUNCTION(scriptContext->GetThreadContext(), thenFinallyFunction->GetOnFinally(), CallInfo(CallFlags_Value, 1), library->GetUndefined());

// 4. Let C be F.[[Constructor]]
// 5. Assert IsConstructor(C)
Assert(JavascriptOperators::IsConstructor(thenFinallyFunction->GetConstructor()));

// 6. Let promise be ? PromiseResolve(c, result)
Var promiseVar = CreateResolvedPromise(result, scriptContext, thenFinallyFunction->GetConstructor());

// 7. Let valueThunk be equivalent to a function that returns value
// OR 7. Let thrower be equivalent to a function that throws reason

Var valueOrReason = nullptr;

if (args.Info.Count > 1)
{
valueOrReason = args[1];
}
else
{
valueOrReason = scriptContext->GetLibrary()->GetUndefined();
}

JavascriptPromiseThunkFinallyFunction* thunkFinallyFunction = library->CreatePromiseThunkFinallyFunction(EntryThunkFinallyFunction, valueOrReason, thenFinallyFunction->GetShouldThrow());

// 8. Return ? Invoke(promise, "then", <<valueThunk>>)
RecyclableObject* promise = JavascriptOperators::ToObject(promiseVar, scriptContext);
Var funcVar = JavascriptOperators::GetProperty(promise, Js::PropertyIds::then, scriptContext);

if (!JavascriptConversion::IsCallable(funcVar))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedFunction, _u("Promise.prototype.finally"));
}

RecyclableObject* func = RecyclableObject::FromVar(funcVar);

return CALL_FUNCTION(scriptContext->GetThreadContext(),
func, Js::CallInfo(CallFlags_Value, 2),
promiseVar,
thunkFinallyFunction);
}

// valueThunk Function as referenced within draft ES2018 #sec-thenfinallyfunctions
// and thrower as referenced within draft ES2018 #sec-catchfinallyfunctions
Var JavascriptPromise::EntryThunkFinallyFunction(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
Assert(!(callInfo.Flags & CallFlags_New));

JavascriptPromiseThunkFinallyFunction* thunkFinallyFunction = JavascriptPromiseThunkFinallyFunction::FromVar(function);

if (!thunkFinallyFunction->GetShouldThrow())
{
return thunkFinallyFunction->GetValue();
}
else
{
JavascriptExceptionOperators::Throw(thunkFinallyFunction->GetValue(), function->GetScriptContext());
}
}

// Promise Reject and Resolve Functions as described in ES 2015 Section 25.4.1.4.1 and 25.4.1.4.2
Var JavascriptPromise::EntryResolveOrRejectFunction(RecyclableObject* function, CallInfo callInfo, ...)
{
Expand Down Expand Up @@ -913,8 +1066,8 @@ namespace Js
{
Assert(args[1] != nullptr);

return args[1];
}
return args[1];
}
else
{
return function->GetScriptContext()->GetLibrary()->GetUndefined();
Expand Down
85 changes: 85 additions & 0 deletions lib/Runtime/Library/JavascriptPromise.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,85 @@ namespace Js
#endif
};

class JavascriptPromiseThenFinallyFunction : public RuntimeFunction
{
protected:
DEFINE_VTABLE_CTOR(JavascriptPromiseThenFinallyFunction, RuntimeFunction);
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(JavascriptPromiseThenFinallyFunction);

public:
JavascriptPromiseThenFinallyFunction(DynamicType* type, FunctionInfo* functionInfo, RecyclableObject* OnFinally, RecyclableObject* Constructor, bool shouldThrow)
: RuntimeFunction(type, functionInfo), OnFinally(OnFinally), Constructor(Constructor), shouldThrow(shouldThrow)
{ }

inline static bool Is(Var var)
{
if (JavascriptFunction::Is(var))
{
JavascriptFunction* obj = JavascriptFunction::UnsafeFromVar(var);

return VirtualTableInfo<JavascriptPromiseThenFinallyFunction>::HasVirtualTable(obj)
|| VirtualTableInfo<CrossSiteObject<JavascriptPromiseThenFinallyFunction>>::HasVirtualTable(obj);
}

return false;
}

inline static JavascriptPromiseThenFinallyFunction* FromVar(Var var)
{
AssertOrFailFast(JavascriptPromiseThenFinallyFunction::Is(var));

return static_cast<JavascriptPromiseThenFinallyFunction*>(var);
}

inline bool GetShouldThrow() { return this->shouldThrow; }
inline RecyclableObject* GetOnFinally() { return this->OnFinally; }
inline RecyclableObject* GetConstructor() { return this->Constructor; }

private:
Field(RecyclableObject*) OnFinally;
Field(RecyclableObject*) Constructor;
Field(bool) shouldThrow;
};

class JavascriptPromiseThunkFinallyFunction : public RuntimeFunction
{
protected:
DEFINE_VTABLE_CTOR(JavascriptPromiseThunkFinallyFunction, RuntimeFunction);
DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(JavascriptPromiseThunkFinallyFunction);

public:
JavascriptPromiseThunkFinallyFunction(DynamicType* type, FunctionInfo* functionInfo, Var value, bool shouldThrow)
: RuntimeFunction(type, functionInfo), value(value), shouldThrow(shouldThrow)
{ }

inline static bool Is(Var var)
{
if (JavascriptFunction::Is(var))
{
JavascriptFunction* obj = JavascriptFunction::UnsafeFromVar(var);

return VirtualTableInfo<JavascriptPromiseThunkFinallyFunction>::HasVirtualTable(obj)
|| VirtualTableInfo<CrossSiteObject<JavascriptPromiseThunkFinallyFunction>>::HasVirtualTable(obj);
}
return false;
}

inline static JavascriptPromiseThunkFinallyFunction* FromVar(Var var)
{
AssertOrFailFast(JavascriptPromiseThunkFinallyFunction::Is(var));

return static_cast<JavascriptPromiseThunkFinallyFunction*>(var);
}

inline bool GetShouldThrow() { return this->shouldThrow; }
inline Var GetValue() { return this->value; }

private:
Field(Var) value;
Field(bool) shouldThrow;
};

struct JavascriptPromiseAllResolveElementFunctionRemainingElementsWrapper
{
Field(uint32) remainingElements;
Expand Down Expand Up @@ -388,10 +467,13 @@ namespace Js
static FunctionInfo Reject;
static FunctionInfo Resolve;
static FunctionInfo Then;
static FunctionInfo Finally;

static FunctionInfo Identity;
static FunctionInfo Thrower;

static FunctionInfo FinallyValueFunction;
static FunctionInfo ThenFinallyFunction;
static FunctionInfo ResolveOrRejectFunction;
static FunctionInfo CapabilitiesExecutorFunction;
static FunctionInfo AllResolveElementFunction;
Expand All @@ -409,7 +491,10 @@ namespace Js
static Var EntryReject(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryResolve(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryThen(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryFinally(RecyclableObject* function, CallInfo callInfo, ...);

static Var EntryThunkFinallyFunction(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryThenFinallyFunction(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryCapabilitiesExecutorFunction(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryResolveOrRejectFunction(RecyclableObject* function, CallInfo callInfo, ...);
static Var EntryReactionTaskFunction(RecyclableObject* function, CallInfo callInfo, ...);
Expand Down
Loading