diff --git a/android/runtime/v8/src/native/V8Runtime.cpp b/android/runtime/v8/src/native/V8Runtime.cpp index 66c986d12a3..b3f51d95a57 100644 --- a/android/runtime/v8/src/native/V8Runtime.cpp +++ b/android/runtime/v8/src/native/V8Runtime.cpp @@ -223,6 +223,7 @@ JNIEXPORT void JNICALL Java_org_appcelerator_kroll_runtime_v8_V8Runtime_nativeIn // isolate->SetAbortOnUncaughtExceptionCallback(ShouldAbortOnUncaughtException); // isolate->SetAutorunMicrotasks(false); // isolate->SetFatalErrorHandler(OnFatalError); + isolate->SetPromiseRejectCallback(V8Util::reportRejection); isolate->SetCaptureStackTraceForUncaughtExceptions(true, 10, v8::StackTrace::kOverview); } else { isolate = V8Runtime::v8_isolate; diff --git a/android/runtime/v8/src/native/V8Util.cpp b/android/runtime/v8/src/native/V8Util.cpp index 36cccaaf9bc..1ef4da82053 100644 --- a/android/runtime/v8/src/native/V8Util.cpp +++ b/android/runtime/v8/src/native/V8Util.cpp @@ -6,6 +6,7 @@ */ #include #include +#include #include "V8Util.h" #include "JNIUtil.h" @@ -157,6 +158,115 @@ void V8Util::reportException(Isolate* isolate, TryCatch &tryCatch, bool showLine LOGE(EXC_TAG, *error); } +static thread_local std::unordered_set*> seenErrors; +static void errorWeakCallback(const v8::WeakCallbackInfo>& info) { + v8::Global* g = info.GetParameter(); + seenErrors.erase(g); + g->Reset(); + delete g; +} +void V8Util::reportRejection(v8::PromiseRejectMessage data) +{ + // Extract main Objects + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope handle_scope(isolate); + v8::Local context = isolate->GetCurrentContext(); + v8::Local value=data.GetValue(); + + // Deduplication Process and Rejection consistency check + v8::PromiseRejectEvent event = data.GetEvent(); + if (event == v8::kPromiseRejectWithNoHandler) { + + // Deduplicate on same value (Error) + for (v8::Global* g : seenErrors) { + if (g->Get(isolate)->StrictEquals(value)) { + LOGD(EXC_TAG, "PromiseRejectEvent duplicated discarded"); + return; + } + } + } else if (event == v8::kPromiseHandlerAddedAfterReject) { + LOGD(EXC_TAG, "PromiseRejectEvent handler added after reject discarded"); + return; + } else { + LOGE(EXC_TAG, "PromiseRejectEvent with unexpected event (%d) Lost", event); + return; + } + + // Value consistency check + if (value.IsEmpty()) { + LOGE(EXC_TAG, "PromiseRejectEvent with Empty Value Lost"); + return; + } + + // Track the Error with Global + weak + v8::Global* g = new v8::Global(isolate, value); + g->SetWeak(g, errorWeakCallback, v8::WeakCallbackType::kParameter); + seenErrors.insert(g); + + // Extract Error message + v8::Local message = v8::Exception::CreateMessage(isolate, value); + v8::String::Utf8Value utf8Message(isolate, message->Get()); + v8::String::Utf8Value utf8ScriptName(isolate, message->GetScriptResourceName()); + v8::Local logSourceLine = message->GetSourceLine(context).ToLocalChecked(); + v8::String::Utf8Value utf8SourceLine(isolate, logSourceLine); + + // Log Error message to Console + LOGE(TAG, "%s", *utf8Message); + LOGE(TAG, "%s @ %d >>> %s", + *utf8ScriptName, + message->GetLineNumber(context).FromMaybe(-1), + *utf8SourceLine); + + // Obtain javascript and java stack traces + Local jsStack=Undefined(isolate); + Local javaStack=Undefined(isolate); + if (value->IsObject()) { + Local error = value.As(); + jsStack = error->Get(context, STRING_NEW(isolate, "stack")).FromMaybe(Undefined(isolate).As()); + javaStack = error->Get(context, STRING_NEW(isolate, "nativeStack")).FromMaybe(Undefined(isolate).As()); + } + + // javascript stack trace not provided? obtain current javascript stack trace + if (jsStack.IsEmpty() || jsStack->IsNullOrUndefined()) { + Local frames = message->GetStackTrace(); + if (frames.IsEmpty() || !frames->GetFrameCount()) { + frames = StackTrace::CurrentStackTrace(isolate, MAX_STACK); + } + if (!frames.IsEmpty()) { + std::string stackString = V8Util::stackTraceString(isolate, frames); + if (!stackString.empty()) { + jsStack = String::NewFromUtf8(isolate, stackString.c_str(), v8::NewStringType::kNormal).ToLocalChecked().As(); + } + } + } + + // Report Exception to JS via krollRuntimeDispatchExceptionMethod + JNIEnv* env = titanium::JNIUtil::getJNIEnv(); + jstring title = env->NewStringUTF("Rejected Promise"); + jstring errorMessage = titanium::TypeConverter::jsValueToJavaString(isolate, env, message->Get()); + jstring resourceName = titanium::TypeConverter::jsValueToJavaString(isolate, env, message->GetScriptResourceName()); + jstring sourceLine = titanium::TypeConverter::jsValueToJavaString(isolate, env, message->GetSourceLine(context).FromMaybe(Null(isolate).As())); + jstring jsStackString = titanium::TypeConverter::jsValueToJavaString(isolate, env, jsStack); + jstring javaStackString = titanium::TypeConverter::jsValueToJavaString(isolate, env, javaStack); + env->CallStaticVoidMethod( + titanium::JNIUtil::krollRuntimeClass, + titanium::JNIUtil::krollRuntimeDispatchExceptionMethod, + title, + errorMessage, + resourceName, + message->GetLineNumber(context).FromMaybe(-1), + sourceLine, + message->GetEndColumn(context).FromMaybe(-1), + jsStackString, + javaStackString); + env->DeleteLocalRef(title); + env->DeleteLocalRef(errorMessage); + env->DeleteLocalRef(resourceName); + env->DeleteLocalRef(sourceLine); + env->DeleteLocalRef(jsStackString); + env->DeleteLocalRef(javaStackString); +} + void V8Util::openJSErrorDialog(Isolate* isolate, TryCatch &tryCatch) { JNIEnv *env = JNIUtil::getJNIEnv(); diff --git a/android/runtime/v8/src/native/V8Util.h b/android/runtime/v8/src/native/V8Util.h index a9623323e7e..0f1fc34bb43 100644 --- a/android/runtime/v8/src/native/V8Util.h +++ b/android/runtime/v8/src/native/V8Util.h @@ -161,6 +161,7 @@ class V8Util { static void objectExtend(v8::Local dest, v8::Local src); // TODO: Remove when we do a breaking change! static void objectExtend(v8::Isolate* isolate, v8::Local dest, v8::Local src); static void reportException(v8::Isolate* isolate, v8::TryCatch &tryCatch, bool showLine = true); + static void reportRejection(v8::PromiseRejectMessage data); static void openJSErrorDialog(v8::Isolate* isolate, v8::TryCatch &tryCatch); static void fatalException(v8::Isolate* isolate, v8::TryCatch &tryCatch); static v8::Local jsonStringify(v8::Isolate* isolate, v8::Local value);