Skip to content

Commit 2029167

Browse files
authored
Merge pull request #35 from NativeScript/feat-promise-rejection-handling
Add global unhandled promise rejection handling
2 parents fce8e68 + 481851c commit 2029167

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+11649
-16550
lines changed

test-app/app/src/main/assets/internal/ts_helpers.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,4 +390,99 @@
390390
},
391391
});
392392
}
393+
394+
const pendingUnhandledRejections = [];
395+
const hasBeenNotifiedProperty = new WeakMap();
396+
function emitPendingUnhandledRejections() {
397+
while (pendingUnhandledRejections.length > 0) {
398+
var promise = pendingUnhandledRejections.shift();
399+
var reason = pendingUnhandledRejections.shift();
400+
if (
401+
hasBeenNotifiedProperty.get(promise) === false &&
402+
globalThis.__onUncaughtError
403+
) {
404+
globalThis.__onUncaughtError(reason);
405+
}
406+
}
407+
}
408+
409+
function unhandledPromise(promise, reason) {
410+
pendingUnhandledRejections.push(promise, reason);
411+
__ns__setTimeout(() => {
412+
emitPendingUnhandledRejections();
413+
}, 1);
414+
}
415+
416+
function handledPromise(promise) {
417+
const hasBeenNotified = hasBeenNotifiedProperty.get(promise);
418+
if (hasBeenNotified != undefined) {
419+
hasBeenNotifiedProperty.delete(promise);
420+
}
421+
}
422+
423+
function makeRejectionError(reason) {
424+
const stringValue = Object.prototype.toString.call(reason);
425+
if (
426+
stringValue === "[object Error]" ||
427+
reason instanceof Error ||
428+
typeof reason === "object"
429+
) {
430+
reason.message = `(Unhandled promise rejection): ${reason.message}`;
431+
432+
if (!reason.stack) {
433+
reason.stack = new Error("").stack;
434+
}
435+
return reason;
436+
} else {
437+
const error = new Error(reason, {
438+
cause: reason,
439+
});
440+
error.message = `(Unhandled promise rejection): ${error.message}`;
441+
return error;
442+
}
443+
}
444+
445+
if (globalThis.__engine === "V8") {
446+
// Only report errors for promise rejections that go unhandled.
447+
globalThis.onUnhandledPromiseRejectionTracker = (
448+
event,
449+
promise,
450+
reason
451+
) => {
452+
if (event === globalThis.__promiseUnhandledEvent) {
453+
hasBeenNotifiedProperty.set(promise, false);
454+
const error = makeRejectionError(reason);
455+
unhandledPromise(promise, error);
456+
} else {
457+
handledPromise(promise);
458+
}
459+
};
460+
} else if (globalThis.__engine === "QuickJS") {
461+
globalThis.onUnhandledPromiseRejectionTracker = (
462+
promise,
463+
reason,
464+
isHandled
465+
) => {
466+
if (!isHandled) {
467+
hasBeenNotifiedProperty.set(promise, false);
468+
const error = makeRejectionError(reason);
469+
// Preserve original stack trace.
470+
if (!promise.then["[[stack]]"]) {
471+
promise.then["[[stack]]"] = error.stack;
472+
} else {
473+
error.stack = promise.then["[[stack]]"];
474+
}
475+
unhandledPromise(promise, error);
476+
} else {
477+
handledPromise(promise);
478+
}
479+
};
480+
} else if (globalThis.__engine === "Hermes") {
481+
HermesInternal.enablePromiseRejectionTracker({
482+
allRejections: true,
483+
onUnhandled: function (id, error) {
484+
globalThis.__onUncaughtError(makeRejectionError(error));
485+
},
486+
});
487+
}
393488
})();

test-app/runtime/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@ if (QUICKJS)
104104
# quickjs
105105
src/main/cpp/napi/quickjs/source/cutils.c
106106
src/main/cpp/napi/quickjs/source/libregexp.c
107-
src/main/cpp/napi/quickjs/source/libbf.c
108107
src/main/cpp/napi/quickjs/source/libunicode.c
109108
src/main/cpp/napi/quickjs/source/quickjs.c
109+
src/main/cpp/napi/quickjs/source/dtoa.c
110110
# napi
111111
src/main/cpp/napi/quickjs/quickjs-api.c
112112
src/main/cpp/napi/quickjs/jsr.cpp

test-app/runtime/src/main/cpp/napi/quickjs/quickjs-api.c

Lines changed: 70 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#include <limits.h>
44
#include <quickjs.h>
55
#include "js_native_api.h"
6-
#include "libbf.h"
76
#include "quicks-runtime.h"
87

98
#ifdef __ANDROID__
@@ -1043,48 +1042,48 @@ bool JS_GetBigIntWords(JSContext *context, JSValue value, int *signBit, size_t *
10431042
return rev;
10441043
}
10451044

1046-
typedef struct JS_BigFloatExt {
1047-
JSRefCountHeader header;
1048-
bf_t num;
1049-
} JS_BigFloatExt;
1050-
1051-
bool JS_ToInt64WithBigInt(JSContext *context, JSValueConst value, int64_t *pres, bool *lossless) {
1052-
if (pres == NULL || lossless == NULL) {
1053-
return 0;
1054-
}
1055-
1056-
bool rev = false;
1057-
JSValue val = JS_DupValue(context, value);
1058-
JS_BigFloatExt *p = (JS_BigFloatExt *) JS_VALUE_GET_PTR(val);
1059-
if (p) {
1060-
int opFlag = bf_get_int64(pres, &p->num, 0);
1061-
if (lossless != NULL) {
1062-
*lossless = (opFlag == 0);
1063-
}
1064-
rev = true;
1065-
}
1066-
JS_FreeValue(context, val);
1067-
return rev;
1068-
}
1069-
1070-
bool JS_ToUInt64WithBigInt(JSContext *context, JSValueConst value, uint64_t *pres, bool *lossless) {
1071-
if (pres == NULL || lossless == NULL) {
1072-
return false;
1073-
}
1074-
1075-
bool rev = false;
1076-
JSValue val = JS_DupValue(context, value);
1077-
JS_BigFloatExt *p = (JS_BigFloatExt *) JS_VALUE_GET_PTR(val);
1078-
if (p) {
1079-
int opFlag = bf_get_uint64(pres, &p->num);
1080-
if (lossless != NULL) {
1081-
*lossless = (opFlag == 0);
1082-
}
1083-
rev = true;
1084-
}
1085-
JS_FreeValue(context, val);
1086-
return rev;
1087-
}
1045+
//typedef struct JS_BigFloatExt {
1046+
// JSRefCountHeader header;
1047+
// bf_t num;
1048+
//} JS_BigFloatExt;
1049+
//
1050+
//bool JS_ToInt64WithBigInt(JSContext *context, JSValueConst value, int64_t *pres, bool *lossless) {
1051+
// if (pres == NULL || lossless == NULL) {
1052+
// return 0;
1053+
// }
1054+
//
1055+
// bool rev = false;
1056+
// JSValue val = JS_DupValue(context, value);
1057+
// JS_BigFloatExt *p = (JS_BigFloatExt *) JS_VALUE_GET_PTR(val);
1058+
// if (p) {
1059+
// int opFlag = bf_get_int64(pres, &p->num, 0);
1060+
// if (lossless != NULL) {
1061+
// *lossless = (opFlag == 0);
1062+
// }
1063+
// rev = true;
1064+
// }
1065+
// JS_FreeValue(context, val);
1066+
// return rev;
1067+
//}
1068+
//
1069+
//bool JS_ToUInt64WithBigInt(JSContext *context, JSValueConst value, uint64_t *pres, bool *lossless) {
1070+
// if (pres == NULL || lossless == NULL) {
1071+
// return false;
1072+
// }
1073+
//
1074+
// bool rev = false;
1075+
// JSValue val = JS_DupValue(context, value);
1076+
// JS_BigFloatExt *p = (JS_BigFloatExt *) JS_VALUE_GET_PTR(val);
1077+
// if (p) {
1078+
// int opFlag = bf_get_uint64(pres, &p->num);
1079+
// if (lossless != NULL) {
1080+
// *lossless = (opFlag == 0);
1081+
// }
1082+
// rev = true;
1083+
// }
1084+
// JS_FreeValue(context, val);
1085+
// return rev;
1086+
//}
10881087

10891088
napi_status napi_create_bigint_int64(napi_env env, int64_t value, napi_value *result) {
10901089
CHECK_ARG(env)
@@ -1508,7 +1507,7 @@ napi_status napi_get_array_length(napi_env env,
15081507

15091508
JSValue jsValue = *((JSValue *) value);
15101509

1511-
if (!JS_IsArray(env->context, jsValue))
1510+
if (!JS_IsArray(jsValue))
15121511
return napi_set_last_error(env, napi_array_expected, NULL, 0, NULL);
15131512

15141513
int64_t length = 0;
@@ -1706,18 +1705,6 @@ napi_status napi_get_typedarray_info(napi_env env,
17061705
return napi_clear_last_error(env);
17071706
}
17081707

1709-
bool JS_IsDataView(JSContext *context, JSValue value) {
1710-
bool result = false;
1711-
JSValue constructor = JS_GetPropertyStr(context, value, "constructor");
1712-
JSValue name = JS_GetPropertyStr(context, constructor, "name");
1713-
const char *cName = JS_ToCString(context, name);
1714-
result = !strcmp("DataView", cName ? cName : "");
1715-
JS_FreeCString(context, cName);
1716-
JS_FreeValue(context, name);
1717-
JS_FreeValue(context, constructor);
1718-
return result;
1719-
}
1720-
17211708
napi_status napi_get_dataview_info(napi_env env,
17221709
napi_value dataview,
17231710
size_t *byte_length,
@@ -1729,7 +1716,7 @@ napi_status napi_get_dataview_info(napi_env env,
17291716

17301717
JSValue value = *((JSValue *) dataview);
17311718

1732-
if (!JS_IsDataView(env->context, value)) {
1719+
if (!JS_IsDataView(value)) {
17331720
return napi_set_last_error(env, napi_invalid_arg, NULL, 0, NULL);
17341721
}
17351722

@@ -1835,11 +1822,11 @@ napi_status napi_get_value_bigint_int64(napi_env env,
18351822
CHECK_ARG(value)
18361823
CHECK_ARG(result)
18371824

1838-
if (!JS_IsBigInt(env->context, *(JSValue *) value)) {
1825+
if (!JS_IsBigInt(*(JSValue *) value)) {
18391826
return napi_set_last_error(env, napi_bigint_expected, NULL, 0, NULL);
18401827
}
18411828

1842-
JS_ToInt64WithBigInt(env->context, *(JSValue *) value, result, lossless);
1829+
JS_ToBigInt64(env->context, result,*(JSValue *) value);
18431830

18441831
return napi_clear_last_error(env);
18451832
}
@@ -1852,11 +1839,11 @@ napi_status napi_get_value_bigint_uint64(napi_env env,
18521839
CHECK_ARG(value)
18531840
CHECK_ARG(result)
18541841

1855-
if (!JS_IsBigInt(env->context, *(JSValue *) value)) {
1842+
if (!JS_IsBigInt(*(JSValue *) value)) {
18561843
return napi_set_last_error(env, napi_bigint_expected, NULL, 0, NULL);
18571844
}
18581845

1859-
JS_ToUInt64WithBigInt(env->context, *(JSValue *) value, result, lossless);
1846+
JS_ToBigUint64(env->context, result, *(JSValue *) value);
18601847

18611848
return napi_clear_last_error(env);
18621849
}
@@ -1874,7 +1861,7 @@ napi_status napi_get_value_bigint_words(napi_env env,
18741861

18751862
JSValue jsValue = *(JSValue *) value;
18761863

1877-
if (!JS_IsBigInt(env->context, jsValue)) {
1864+
if (!JS_IsBigInt(jsValue)) {
18781865
return napi_set_last_error(env, napi_bigint_expected, NULL, 0, NULL);
18791866
}
18801867

@@ -2271,7 +2258,7 @@ napi_status napi_typeof(napi_env env, napi_value value, napi_valuetype *result)
22712258
*result = napi_string;
22722259
} else if (JS_IsSymbol(jsValue)) {
22732260
*result = napi_symbol;
2274-
} else if (JS_IsBigInt(env->context, jsValue)) {
2261+
} else if (JS_IsBigInt(jsValue)) {
22752262
*result = napi_bigint;
22762263
} else if (JS_IsFunction(env->context, jsValue)) {
22772264
*result = napi_function;
@@ -2319,7 +2306,7 @@ napi_status napi_is_array(napi_env env, napi_value value, bool *result) {
23192306
CHECK_ARG(result)
23202307

23212308
JSValue jsValue = *((JSValue *) value);
2322-
int status = JS_IsArray(env->context, jsValue);
2309+
int status = JS_IsArray(jsValue);
23232310
RETURN_STATUS_IF_FALSE(status != -1, napi_pending_exception);
23242311
*result = status;
23252312

@@ -2388,7 +2375,7 @@ napi_status napi_is_error(napi_env env, napi_value value, bool *result) {
23882375
CHECK_ARG(value)
23892376
CHECK_ARG(result)
23902377

2391-
int status = JS_IsError(env->context, *((JSValue *) value));
2378+
int status = JS_IsError(*((JSValue *) value));
23922379
*result = status;
23932380
return napi_clear_last_error(env);
23942381
}
@@ -2411,7 +2398,7 @@ napi_status napi_is_dataview(napi_env env, napi_value value, bool *result) {
24112398
CHECK_ARG(value)
24122399
CHECK_ARG(result)
24132400

2414-
int status = JS_IsDataView(env->context, *((JSValue *) value));
2401+
int status = JS_IsDataView(*((JSValue *) value));
24152402
*result = status;
24162403

24172404
return napi_clear_last_error(env);
@@ -4001,23 +3988,6 @@ napi_status qjs_create_runtime(napi_runtime *runtime) {
40013988
return napi_ok;
40023989
}
40033990

4004-
static void JS_AfterGCCallback(JSRuntime *rt) {
4005-
napi_env env = (napi_env) JS_GetRuntimeOpaque(rt);
4006-
if (env->gcAfter != NULL) {
4007-
env->gcAfter->finalizeCallback(env, env->gcAfter->data, env->gcAfter->finalizeHint);
4008-
}
4009-
}
4010-
4011-
static int JS_BeforeGCCallback(JSRuntime *rt) {
4012-
napi_env env = (napi_env) JS_GetRuntimeOpaque(rt);
4013-
bool hint = true;
4014-
if (env->gcAfter != NULL) {
4015-
env->gcAfter->finalizeCallback(env, env->gcAfter->data, &hint);
4016-
}
4017-
4018-
return hint;
4019-
}
4020-
40213991
static JSValue JSRunGCCallback(JSContext *ctx, JSValue
40223992
this_val,
40233993
int argc, JSValue
@@ -4047,6 +4017,21 @@ JSEngineCallback(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *
40474017
return JS_UNDEFINED;
40484018
}
40494019

4020+
void JSR_PromiseRejectionTracker(JSContext *ctx, JSValue promise,
4021+
JSValue reason,
4022+
bool is_handled, void *opaque) {
4023+
JSValue global = JS_GetGlobalObject(ctx);
4024+
JSValue onUnhandledRejection = JS_GetPropertyStr(ctx, global, "onUnhandledPromiseRejectionTracker");
4025+
if (JS_IsFunction(ctx, onUnhandledRejection)) {
4026+
JSValue isHandled = JS_NewBool(ctx, is_handled);
4027+
JSValue argv[3] = {promise, reason, isHandled};
4028+
JS_Call(ctx, onUnhandledRejection, global, 3, argv);
4029+
JS_FreeValue(ctx, isHandled);
4030+
}
4031+
JS_FreeValue(ctx, onUnhandledRejection);
4032+
JS_FreeValue(ctx, global);
4033+
}
4034+
40504035
napi_status qjs_create_napi_env(napi_env *env, napi_runtime runtime) {
40514036
assert(env && runtime);
40524037

@@ -4064,10 +4049,6 @@ napi_status qjs_create_napi_env(napi_env *env, napi_runtime runtime) {
40644049

40654050
JS_SetRuntimeOpaque(runtime->runtime, *env);
40664051

4067-
JS_SetGCAfterCallback(runtime->runtime, JS_AfterGCCallback);
4068-
4069-
JS_SetGCBeforeCallback(runtime->runtime, JS_BeforeGCCallback);
4070-
40714052
// Create runtime atoms
40724053
(*env)->atoms.napi_external = JS_NewAtom(context, "napi_external");
40734054
(*env)->atoms.registerFinalizer = JS_NewAtom(context, "register");
@@ -4106,6 +4087,8 @@ napi_status qjs_create_napi_env(napi_env *env, napi_runtime runtime) {
41064087
JSValue EngineCallback = JS_NewCFunction(context, JSEngineCallback, NULL, 0);
41074088
JS_SetPropertyStr(context, globalValue, "directFunction", EngineCallback);
41084089

4090+
JS_SetHostPromiseRejectionTracker(runtime->runtime, JSR_PromiseRejectionTracker, *env);
4091+
41094092
(*env)->instanceData = NULL;
41104093
(*env)->isThrowNull = false;
41114094
(*env)->gcBefore = NULL;

0 commit comments

Comments
 (0)