Skip to content

Commit 000ac90

Browse files
committed
[MERGE #4697 @kfarnung] Backport HostPromiseRejection
Merge pull request #4697 from kfarnung:promisereject Backporting the HostPromiseRejection change to release/1.9
2 parents 70ad0bd + d36457c commit 000ac90

16 files changed

+336
-0
lines changed

Diff for: bin/ChakraCore/ChakraCore.def

+2
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,5 @@ JsLessThan
6262
JsLessThanOrEqual
6363

6464
JsCreateEnhancedFunction
65+
66+
JsSetHostPromiseRejectionTracker

Diff for: bin/ch/ChakraRtInterface.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ bool ChakraRTInterface::LoadChakraDll(ArgInfo* argInfo, HINSTANCE *outLibrary)
120120
m_jsApiHooks.pfJsrtGetValueType = (JsAPIHooks::JsrtGetValueType)GetChakraCoreSymbol(library, "JsGetValueType");
121121
m_jsApiHooks.pfJsrtSetIndexedProperty = (JsAPIHooks::JsrtSetIndexedPropertyPtr)GetChakraCoreSymbol(library, "JsSetIndexedProperty");
122122
m_jsApiHooks.pfJsrtSetPromiseContinuationCallback = (JsAPIHooks::JsrtSetPromiseContinuationCallbackPtr)GetChakraCoreSymbol(library, "JsSetPromiseContinuationCallback");
123+
m_jsApiHooks.pfJsrtSetHostPromiseRejectionTracker = (JsAPIHooks::JsrtSetHostPromiseRejectionTrackerPtr)GetChakraCoreSymbol(library, "JsSetHostPromiseRejectionTracker");
123124
m_jsApiHooks.pfJsrtGetContextOfObject = (JsAPIHooks::JsrtGetContextOfObject)GetChakraCoreSymbol(library, "JsGetContextOfObject");
124125
m_jsApiHooks.pfJsrtInitializeModuleRecord = (JsAPIHooks::JsInitializeModuleRecordPtr)GetChakraCoreSymbol(library, "JsInitializeModuleRecord");
125126
m_jsApiHooks.pfJsrtParseModuleSource = (JsAPIHooks::JsParseModuleSourcePtr)GetChakraCoreSymbol(library, "JsParseModuleSource");

Diff for: bin/ch/ChakraRtInterface.h

+3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ struct JsAPIHooks
5454
typedef JsErrorCode (WINAPI *JsrtGetValueType)(JsValueRef value, JsValueType *type);
5555
typedef JsErrorCode (WINAPI *JsrtSetIndexedPropertyPtr)(JsValueRef object, JsValueRef index, JsValueRef value);
5656
typedef JsErrorCode (WINAPI *JsrtSetPromiseContinuationCallbackPtr)(JsPromiseContinuationCallback callback, void *callbackState);
57+
typedef JsErrorCode (WINAPI *JsrtSetHostPromiseRejectionTrackerPtr)(JsHostPromiseRejectionTrackerCallback callback, void *callbackState);
5758
typedef JsErrorCode (WINAPI *JsrtGetContextOfObject)(JsValueRef object, JsContextRef *callbackState);
5859

5960
typedef JsErrorCode(WINAPI *JsrtDiagStartDebugging)(JsRuntimeHandle runtimeHandle, JsDiagDebugEventCallback debugEventCallback, void* callbackState);
@@ -152,6 +153,7 @@ struct JsAPIHooks
152153
JsrtGetValueType pfJsrtGetValueType;
153154
JsrtSetIndexedPropertyPtr pfJsrtSetIndexedProperty;
154155
JsrtSetPromiseContinuationCallbackPtr pfJsrtSetPromiseContinuationCallback;
156+
JsrtSetHostPromiseRejectionTrackerPtr pfJsrtSetHostPromiseRejectionTracker;
155157
JsrtGetContextOfObject pfJsrtGetContextOfObject;
156158
JsrtDiagStartDebugging pfJsrtDiagStartDebugging;
157159
JsrtDiagStopDebugging pfJsrtDiagStopDebugging;
@@ -356,6 +358,7 @@ class ChakraRTInterface
356358
static JsErrorCode WINAPI JsGetValueType(JsValueRef value, JsValueType *type) { return HOOK_JS_API(GetValueType(value, type)); }
357359
static JsErrorCode WINAPI JsSetIndexedProperty(JsValueRef object, JsValueRef index, JsValueRef value) { return HOOK_JS_API(SetIndexedProperty(object, index, value)); }
358360
static JsErrorCode WINAPI JsSetPromiseContinuationCallback(JsPromiseContinuationCallback callback, void *callbackState) { return HOOK_JS_API(SetPromiseContinuationCallback(callback, callbackState)); }
361+
static JsErrorCode WINAPI JsSetHostPromiseRejectionTracker(JsHostPromiseRejectionTrackerCallback callback, void *callbackState) { return HOOK_JS_API(SetHostPromiseRejectionTracker(callback, callbackState)); }
359362
static JsErrorCode WINAPI JsGetContextOfObject(JsValueRef object, JsContextRef* context) { return HOOK_JS_API(GetContextOfObject(object, context)); }
360363
static JsErrorCode WINAPI JsDiagStartDebugging(JsRuntimeHandle runtimeHandle, JsDiagDebugEventCallback debugEventCallback, void* callbackState) { return HOOK_JS_API(DiagStartDebugging(runtimeHandle, debugEventCallback, callbackState)); }
361364
static JsErrorCode WINAPI JsDiagStopDebugging(JsRuntimeHandle runtimeHandle, void** callbackState) { return HOOK_JS_API(DiagStopDebugging(runtimeHandle, callbackState)); }

Diff for: bin/ch/HostConfigFlagsList.h

+1
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ FLAG(bool, IgnoreScriptErrorCode, "Don't return error code on script e
1515
FLAG(bool, MuteHostErrorMsg, "Mute host error output, e.g. module load failures", false)
1616
FLAG(bool, TraceHostCallback, "Output traces for host callbacks", false)
1717
FLAG(bool, Test262, "load Test262 harness", false)
18+
FLAG(bool, TrackRejectedPromises, "Enable tracking of unhandled promise rejections", false)
1819
#undef FLAG
1920
#endif

Diff for: bin/ch/WScriptJsrt.cpp

+29
Original file line numberDiff line numberDiff line change
@@ -1726,3 +1726,32 @@ void WScriptJsrt::PromiseContinuationCallback(JsValueRef task, void *callbackSta
17261726
WScriptJsrt::CallbackMessage *msg = new WScriptJsrt::CallbackMessage(0, task);
17271727
messageQueue->InsertSorted(msg);
17281728
}
1729+
1730+
void WScriptJsrt::PromiseRejectionTrackerCallback(JsValueRef promise, JsValueRef reason, bool handled, void *callbackState)
1731+
{
1732+
Assert(promise != JS_INVALID_REFERENCE);
1733+
Assert(reason != JS_INVALID_REFERENCE);
1734+
JsValueRef strValue;
1735+
JsErrorCode error = ChakraRTInterface::JsConvertValueToString(reason, &strValue);
1736+
1737+
if (!handled)
1738+
{
1739+
wprintf(_u("Uncaught promise rejection\n"));
1740+
}
1741+
else
1742+
{
1743+
wprintf(_u("Promise rejection handled\n"));
1744+
}
1745+
1746+
if (error == JsNoError)
1747+
{
1748+
AutoString str(strValue);
1749+
if (str.GetError() == JsNoError)
1750+
{
1751+
wprintf(_u("%ls\n"), str.GetWideString());
1752+
}
1753+
}
1754+
1755+
fflush(stdout);
1756+
}
1757+

Diff for: bin/ch/WScriptJsrt.h

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class WScriptJsrt
5858
static JsErrorCode NotifyModuleReadyCallback(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar);
5959
static JsErrorCode InitializeModuleCallbacks();
6060
static void CALLBACK PromiseContinuationCallback(JsValueRef task, void *callbackState);
61+
static void CALLBACK PromiseRejectionTrackerCallback(JsValueRef promise, JsValueRef reason, bool handled, void *callbackState);
6162

6263
static LPCWSTR ConvertErrorCodeToMessage(JsErrorCode errorCode)
6364
{

Diff for: bin/ch/ch.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,11 @@ HRESULT ExecuteTest(const char* fileName)
757757
IfFailGo(E_FAIL);
758758
}
759759

760+
if (HostConfigFlags::flags.TrackRejectedPromises)
761+
{
762+
ChakraRTInterface::JsSetHostPromiseRejectionTracker(WScriptJsrt::PromiseRejectionTrackerCallback, nullptr);
763+
}
764+
760765
len = strlen(fullPath);
761766
if (HostConfigFlags::flags.GenerateLibraryByteCodeHeaderIsEnabled)
762767
{

Diff for: lib/Jsrt/ChakraCore.h

+43
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,27 @@ typedef struct JsNativeFunctionInfo
129129
/// <returns>The result of the call, if any.</returns>
130130
typedef _Ret_maybenull_ JsValueRef(CHAKRA_CALLBACK * JsEnhancedNativeFunction)(_In_ JsValueRef callee, _In_ JsValueRef *arguments, _In_ unsigned short argumentCount, _In_ JsNativeFunctionInfo *info, _In_opt_ void *callbackState);
131131

132+
/// <summary>
133+
/// A Promise Rejection Tracker callback.
134+
/// </summary>
135+
/// <remarks>
136+
/// The host can specify a promise rejection tracker callback in <c>JsSetHostPromiseRejectionTracker</c>.
137+
/// If a promise is rejected with no reactions or a reaction is added to a promise that was rejected
138+
/// before it had reactions by default nothing is done.
139+
/// A Promise Rejection Tracker callback may be set - which will then be called when this occurs.
140+
/// Note - per draft ECMASpec 2018 25.4.1.9 this function should not set or return an exception
141+
/// Note also the promise and reason parameters may be garbage collected after this function is called
142+
/// if you wish to make further use of them you will need to use JsAddRef to preserve them
143+
/// However if you use JsAddRef you must also call JsRelease and not hold unto them after
144+
/// a handled notification (both per spec and to avoid memory leaks)
145+
/// </remarks>
146+
/// <param name="promise">The promise object, represented as a JsValueRef.</param>
147+
/// <param name="reason">The value/cause of the rejection, represented as a JsValueRef.</param>
148+
/// <param name="handled">Boolean - false for promiseRejected: i.e. if the promise has just been rejected with no handler,
149+
/// true for promiseHandled: i.e. if it was rejected before without a handler and is now being handled.</param>
150+
/// <param name="callbackState">The state passed to <c>JsSetHostPromiseRejectionTracker</c>.</param>
151+
typedef void (CHAKRA_CALLBACK *JsHostPromiseRejectionTrackerCallback)(_In_ JsValueRef promise, _In_ JsValueRef reason, _In_ bool handled, _In_opt_ void *callbackState);
152+
132153
/// <summary>
133154
/// Creates a new enhanced JavaScript function.
134155
/// </summary>
@@ -992,5 +1013,27 @@ CHAKRA_API
9921013
_In_ JsValueRef object,
9931014
_In_ JsValueRef key,
9941015
_Out_ bool *hasOwnProperty);
1016+
1017+
/// <summary>
1018+
/// Sets whether any action should be taken when a promise is rejected with no reactions
1019+
/// or a reaction is added to a promise that was rejected before it had reactions.
1020+
/// By default in either of these cases nothing occurs.
1021+
/// This function allows you to specify if something should occur and provide a callback
1022+
/// to implement whatever should occur.
1023+
/// </summary>
1024+
/// <remarks>
1025+
/// Requires an active script context.
1026+
/// </remarks>
1027+
/// <param name="promiseRejectionTrackerCallback">The callback function being set.</param>
1028+
/// <param name="callbackState">
1029+
/// User provided state that will be passed back to the callback.
1030+
/// </param>
1031+
/// <returns>
1032+
/// The code <c>JsNoError</c> if the operation succeeded, a failure code otherwise.
1033+
/// </returns>
1034+
CHAKRA_API
1035+
JsSetHostPromiseRejectionTracker(
1036+
_In_ JsHostPromiseRejectionTrackerCallback promiseRejectionTrackerCallback,
1037+
_In_opt_ void *callbackState);
9951038
#endif // _CHAKRACOREBUILD
9961039
#endif // _CHAKRACORE_H_

Diff for: lib/Jsrt/Jsrt.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -5331,4 +5331,13 @@ CHAKRA_API JsGetDataViewInfo(
53315331
END_JSRT_NO_EXCEPTION
53325332
}
53335333

5334+
CHAKRA_API JsSetHostPromiseRejectionTracker(_In_ JsHostPromiseRejectionTrackerCallback promiseRejectionTrackerCallback, _In_opt_ void *callbackState)
5335+
{
5336+
return ContextAPINoScriptWrapper_NoRecord([&](Js::ScriptContext *scriptContext) -> JsErrorCode {
5337+
scriptContext->GetLibrary()->SetNativeHostPromiseRejectionTrackerCallback((Js::JavascriptLibrary::HostPromiseRejectionTrackerCallback) promiseRejectionTrackerCallback, callbackState);
5338+
return JsNoError;
5339+
},
5340+
/*allowInObjectBeforeCollectCallback*/true);
5341+
}
5342+
53345343
#endif // _CHAKRACOREBUILD

Diff for: lib/Runtime/Library/JavascriptLibrary.cpp

+26
Original file line numberDiff line numberDiff line change
@@ -5300,6 +5300,32 @@ namespace Js
53005300
this->nativeHostPromiseContinuationFunctionState = state;
53015301
}
53025302

5303+
void JavascriptLibrary::SetNativeHostPromiseRejectionTrackerCallback(HostPromiseRejectionTrackerCallback function, void *state)
5304+
{
5305+
this->nativeHostPromiseRejectionTracker = function;
5306+
this->nativeHostPromiseContinuationFunctionState = state;
5307+
}
5308+
5309+
void JavascriptLibrary::CallNativeHostPromiseRejectionTracker(Var promise, Var reason, bool handled)
5310+
{
5311+
if (this->nativeHostPromiseRejectionTracker != nullptr)
5312+
{
5313+
BEGIN_LEAVE_SCRIPT(scriptContext);
5314+
try
5315+
{
5316+
this->nativeHostPromiseRejectionTracker(promise, reason, handled, this->nativeHostPromiseContinuationFunctionState);
5317+
}
5318+
catch (...)
5319+
{
5320+
// Hosts are required not to pass exceptions back across the callback boundary. If
5321+
// this happens, it is a bug in the host, not something that we are expected to
5322+
// handle gracefully.
5323+
Js::Throw::FatalInternalError();
5324+
}
5325+
END_LEAVE_SCRIPT(scriptContext);
5326+
}
5327+
}
5328+
53035329
void JavascriptLibrary::SetJsrtContext(FinalizableObject* jsrtContext)
53045330
{
53055331
// With JsrtContext supporting cross context, ensure that it doesn't get GCed

Diff for: lib/Runtime/Library/JavascriptLibrary.h

+6
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ namespace Js
242242
static DWORD GetRandSeed1Offset() { return offsetof(JavascriptLibrary, randSeed1); }
243243
static DWORD GetTypeDisplayStringsOffset() { return offsetof(JavascriptLibrary, typeDisplayStrings); }
244244
typedef bool (CALLBACK *PromiseContinuationCallback)(Var task, void *callbackState);
245+
typedef void (CALLBACK *HostPromiseRejectionTrackerCallback)(Var promise, Var reason, bool handled, void *callbackState);
245246

246247
Var GetUndeclBlockVar() const { return undeclBlockVarSentinel; }
247248
bool IsUndeclBlockVar(Var var) const { return var == undeclBlockVarSentinel; }
@@ -491,6 +492,9 @@ namespace Js
491492
FieldNoBarrier(PromiseContinuationCallback) nativeHostPromiseContinuationFunction;
492493
Field(void *) nativeHostPromiseContinuationFunctionState;
493494

495+
FieldNoBarrier(HostPromiseRejectionTrackerCallback) nativeHostPromiseRejectionTracker = nullptr;
496+
Field(void *) nativeHostPromiseRejectionTrackerState;
497+
494498
typedef SList<Js::FunctionProxy*, Recycler> FunctionReferenceList;
495499
typedef JsUtil::WeakReferenceDictionary<uintptr_t, DynamicType, DictionarySizePolicy<PowerOf2Policy, 1>> JsrtExternalTypesCache;
496500

@@ -948,6 +952,8 @@ namespace Js
948952
JavascriptFunction* GetThrowerFunction() const { return throwerFunction; }
949953

950954
void SetNativeHostPromiseContinuationFunction(PromiseContinuationCallback function, void *state);
955+
void SetNativeHostPromiseRejectionTrackerCallback(HostPromiseRejectionTrackerCallback function, void *state);
956+
void CallNativeHostPromiseRejectionTracker(Var promise, Var reason, bool handled);
951957

952958
void SetJsrtContext(FinalizableObject* jsrtContext);
953959
FinalizableObject* GetJsrtContext();

Diff for: lib/Runtime/Library/JavascriptPromise.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace Js
1212
Assert(type->GetTypeId() == TypeIds_Promise);
1313

1414
this->status = PromiseStatusCode_Undefined;
15+
this->isHandled = false;
1516
this->result = nullptr;
1617
this->resolveReactions = nullptr;
1718
this->rejectReactions = nullptr;
@@ -660,6 +661,10 @@ namespace Js
660661
{
661662
reactions = this->GetRejectReactions();
662663
newStatus = PromiseStatusCode_HasRejection;
664+
if (!GetIsHandled())
665+
{
666+
scriptContext->GetLibrary()->CallNativeHostPromiseRejectionTracker(this, resolution, false);
667+
}
663668
}
664669
else
665670
{
@@ -838,13 +843,19 @@ namespace Js
838843
EnqueuePromiseReactionTask(resolveReaction, sourcePromise->result, scriptContext);
839844
break;
840845
case PromiseStatusCode_HasRejection:
846+
if (!sourcePromise->GetIsHandled())
847+
{
848+
scriptContext->GetLibrary()->CallNativeHostPromiseRejectionTracker(sourcePromise, sourcePromise->result, true);
849+
}
841850
EnqueuePromiseReactionTask(rejectReaction, sourcePromise->result, scriptContext);
842851
break;
843852
default:
844853
AssertMsg(false, "Promise status is in an invalid state");
845854
break;
846855
}
847856

857+
sourcePromise->SetIsHandled();
858+
848859
return promiseCapability->GetPromise();
849860
}
850861

Diff for: lib/Runtime/Library/JavascriptPromise.h

+3
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,8 @@ namespace Js
461461
PromiseStatusCode_HasRejection
462462
};
463463

464+
bool GetIsHandled() { return isHandled; }
465+
void SetIsHandled() { isHandled = true; }
464466
PromiseStatus GetStatus() const { return status; }
465467
Var GetResult() const { return result; }
466468

@@ -469,6 +471,7 @@ namespace Js
469471

470472
protected:
471473
Field(PromiseStatus) status;
474+
Field(bool) isHandled;
472475
Field(Var) result;
473476
Field(JavascriptPromiseReactionList*) resolveReactions;
474477
Field(JavascriptPromiseReactionList*) rejectReactions;

Diff for: test/es7/PromiseRejectionTracking.baseline

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Executing test #1 - Reject promise with no reactions.
2+
Uncaught promise rejection
3+
Rejection from test 1
4+
Executing test #2 - Reject promise with a catch reaction only.
5+
Executing test #3 - Reject promise with catch and then reactions.
6+
Executing test #4 - Reject promise then add a catch afterwards.
7+
Uncaught promise rejection
8+
Rejection from test 4
9+
Promise rejection handled
10+
Rejection from test 4
11+
Executing test #5 - Reject promise then add two catches afterwards.
12+
Uncaught promise rejection
13+
Rejection from test 5
14+
Promise rejection handled
15+
Rejection from test 5
16+
Executing test #6 - Async function that throws.
17+
Uncaught promise rejection
18+
Rejection from test 6
19+
Executing test #7 - Async function that throws but is caught.
20+
Uncaught promise rejection
21+
Rejection from test 7
22+
Promise rejection handled
23+
Rejection from test 7
24+
Executing test #8 - Async function that awaits a function that throws.
25+
Uncaught promise rejection
26+
Rejection from test 8
27+
Promise rejection handled
28+
Rejection from test 8
29+
Executing test #9 - Reject a handled promise then handle one of the handles but not the other.
30+
Executing test #10 - Reject a handled promise and don't handle either path.
31+
Begin async results:
32+
Uncaught promise rejection
33+
Rejection from test 8
34+
Uncaught promise rejection
35+
Rejection from test 9
36+
Uncaught promise rejection
37+
Rejection from test 10
38+
Uncaught promise rejection
39+
Rejection from test 10
40+
Promise rejection handled
41+
Rejection from test 9
42+
Uncaught promise rejection
43+
Rejection from test 9

0 commit comments

Comments
 (0)