diff --git a/test-app/runtime/src/main/cpp/NativeScriptException.cpp b/test-app/runtime/src/main/cpp/NativeScriptException.cpp index d5b69df62..3b1105708 100644 --- a/test-app/runtime/src/main/cpp/NativeScriptException.cpp +++ b/test-app/runtime/src/main/cpp/NativeScriptException.cpp @@ -1,438 +1,514 @@ -#include "Util.h" #include "NativeScriptException.h" -#include "V8GlobalHelpers.h" + +#include + #include "ArgConverter.h" #include "NativeScriptAssert.h" -#include "V8StringConstants.h" #include "Runtime.h" -#include +#include "Util.h" +#include "V8GlobalHelpers.h" +#include "V8StringConstants.h" using namespace std; using namespace tns; using namespace v8; NativeScriptException::NativeScriptException(JEnv& env) - : - m_javascriptException(nullptr) { - m_javaException = JniLocalRef(env.ExceptionOccurred()); - env.ExceptionClear(); + : m_javascriptException(nullptr) { + m_javaException = JniLocalRef(env.ExceptionOccurred()); + env.ExceptionClear(); } NativeScriptException::NativeScriptException(const string& message) - : - m_javascriptException(nullptr), m_javaException(JniLocalRef()), m_message(message) { -} - -NativeScriptException::NativeScriptException(const string& message, const string& stackTrace) - : - m_javascriptException(nullptr), m_javaException(JniLocalRef()), m_message(message), m_stackTrace(stackTrace) { -} - -NativeScriptException::NativeScriptException(TryCatch& tc, const string& message) - : - m_javaException(JniLocalRef()) { - auto isolate = Isolate::GetCurrent(); - m_javascriptException = new Persistent(isolate, tc.Exception()); - auto ex = tc.Exception(); - m_message = GetErrorMessage(tc.Message(), ex, message); - m_stackTrace = GetErrorStackTrace(tc.Message()->GetStackTrace()); - m_fullMessage = GetFullMessage(tc, m_message); - tc.Reset(); + : m_javascriptException(nullptr), + m_javaException(JniLocalRef()), + m_message(message) {} + +NativeScriptException::NativeScriptException(const string& message, + const string& stackTrace) + : m_javascriptException(nullptr), + m_javaException(JniLocalRef()), + m_message(message), + m_stackTrace(stackTrace) {} + +NativeScriptException::NativeScriptException(TryCatch& tc, + const string& message) + : m_javaException(JniLocalRef()) { + auto isolate = Isolate::GetCurrent(); + m_javascriptException = new Persistent(isolate, tc.Exception()); + auto ex = tc.Exception(); + m_message = GetErrorMessage(tc.Message(), ex, message); + m_stackTrace = GetErrorStackTrace(tc.Message()->GetStackTrace()); + m_fullMessage = GetFullMessage(tc, m_message); + tc.Reset(); } void NativeScriptException::ReThrowToV8() { - auto isolate = Isolate::GetCurrent(); - auto context = isolate->GetCurrentContext(); - Local errObj; - - if (m_javascriptException != nullptr) { - errObj = Local::New(isolate, *m_javascriptException); - if (errObj->IsObject()) { - if (!m_fullMessage.empty()) { - errObj.As()->Set(context, ArgConverter::ConvertToV8String(isolate, "fullMessage"), ArgConverter::ConvertToV8String(isolate, m_fullMessage)); - } else if (!m_message.empty()) { - errObj.As()->Set(context, ArgConverter::ConvertToV8String(isolate, "fullMessage"), ArgConverter::ConvertToV8String(isolate, m_message)); - } - } - } else if (!m_fullMessage.empty()) { - errObj = Exception::Error(ArgConverter::ConvertToV8String(isolate, m_fullMessage)); - } else if (!m_message.empty()) { - errObj = Exception::Error(ArgConverter::ConvertToV8String(isolate, m_message)); - } else if (!m_javaException.IsNull()) { - errObj = WrapJavaToJsException(); - } else { - errObj = Exception::Error(ArgConverter::ConvertToV8String(isolate, "No javascript exception or message provided.")); + auto isolate = Isolate::GetCurrent(); + auto context = isolate->GetCurrentContext(); + Local errObj; + + if (m_javascriptException != nullptr) { + errObj = Local::New(isolate, *m_javascriptException); + if (errObj->IsObject()) { + if (!m_fullMessage.empty()) { + errObj.As()->Set( + context, ArgConverter::ConvertToV8String(isolate, "fullMessage"), + ArgConverter::ConvertToV8String(isolate, m_fullMessage)); + } else if (!m_message.empty()) { + errObj.As()->Set( + context, ArgConverter::ConvertToV8String(isolate, "fullMessage"), + ArgConverter::ConvertToV8String(isolate, m_message)); + } } - - isolate->ThrowException(errObj); + } else if (!m_fullMessage.empty()) { + errObj = Exception::Error( + ArgConverter::ConvertToV8String(isolate, m_fullMessage)); + } else if (!m_message.empty()) { + errObj = + Exception::Error(ArgConverter::ConvertToV8String(isolate, m_message)); + } else if (!m_javaException.IsNull()) { + errObj = WrapJavaToJsException(); + } else { + errObj = Exception::Error(ArgConverter::ConvertToV8String( + isolate, "No javascript exception or message provided.")); + } + + isolate->ThrowException(errObj); } void NativeScriptException::ReThrowToJava() { - jthrowable ex = nullptr; - JEnv env; + jthrowable ex = nullptr; + JEnv env; + + auto isolate = Isolate::GetCurrent(); + if (!m_javaException.IsNull()) { + auto excClassName = ObjectManager::GetClassName((jobject)m_javaException); + if (excClassName == "com/tns/NativeScriptException") { + ex = m_javaException; + } else { + JniLocalRef msg(env.NewStringUTF("Java Error!")); + ex = static_cast(env.NewObject( + NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID, + (jstring)msg, (jobject)m_javaException)); + } + } else if (m_javascriptException != nullptr) { + /* + * If exception is an object, then check if it has 'nativeException' + * property and it if does then use it. If the found 'nativeException' is of + * type different than com.tns.NativeScript then we should wrap it and keep + * the JavaScript callstack as a message. + * Otherwise create we have to create new exception object. + */ auto isolate = Isolate::GetCurrent(); + auto errObj = Local::New(isolate, *m_javascriptException); + if (errObj->IsObject()) { + auto exObj = TryGetJavaThrowableObject(env, errObj.As()); + ex = (jthrowable)exObj.Move(); + } + JniLocalRef msg(env.NewStringUTF(m_message.c_str())); + JniLocalRef stackTrace(env.NewStringUTF(m_stackTrace.c_str())); - if (!m_javaException.IsNull()) { - auto excClassName = ObjectManager::GetClassName((jobject) m_javaException); - if (excClassName == "com/tns/NativeScriptException") { - ex = m_javaException; - } else { - JniLocalRef msg(env.NewStringUTF("Java Error!")); - ex = static_cast(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID, (jstring) msg, (jobject) m_javaException)); - } - } else if (m_javascriptException != nullptr) { - /* - * If exception is an object, then check if it has 'nativeException' property and it if does then use it. - * If the found 'nativeException' is of type different than com.tns.NativeScript then we should wrap it and keep - * the JavaScript callstack as a message. - * Otherwise create we have to create new exception object. - */ - auto isolate = Isolate::GetCurrent(); - auto errObj = Local::New(isolate, *m_javascriptException); - if (errObj->IsObject()) { - auto exObj = TryGetJavaThrowableObject(env, errObj.As()); - ex = (jthrowable) exObj.Move(); - } - - JniLocalRef msg(env.NewStringUTF(m_message.c_str())); - JniLocalRef stackTrace(env.NewStringUTF(m_stackTrace.c_str())); - - if (ex == nullptr) { - ex = static_cast(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring) msg, (jstring)stackTrace, reinterpret_cast(m_javascriptException))); - } else { - auto excClassName = ObjectManager::GetClassName(ex); - if (excClassName != "com/tns/NativeScriptException") { - ex = static_cast(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID, (jstring) msg, (jstring)stackTrace, ex)); - } - } - } else if (!m_message.empty()) { - JniLocalRef msg(env.NewStringUTF(m_message.c_str())); - JniLocalRef stackTrace(env.NewStringUTF(m_stackTrace.c_str())); - ex = static_cast(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring) msg, (jstring)stackTrace, (jlong) 0)); + if (ex == nullptr) { + ex = static_cast(env.NewObject( + NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, + (jstring)msg, (jstring)stackTrace, + reinterpret_cast(m_javascriptException))); } else { - JniLocalRef msg(env.NewStringUTF("No java exception or message provided.")); - ex = static_cast(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring) msg, (jstring) nullptr, (jlong) 0)); + auto excClassName = ObjectManager::GetClassName(ex); + if (excClassName != "com/tns/NativeScriptException") { + ex = static_cast( + env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, + NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID, (jstring)msg, + (jstring)stackTrace, ex)); + } } - env.Throw(ex); + } else if (!m_message.empty()) { + JniLocalRef msg(env.NewStringUTF(m_message.c_str())); + JniLocalRef stackTrace(env.NewStringUTF(m_stackTrace.c_str())); + ex = static_cast(env.NewObject( + NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, + (jstring)msg, (jstring)stackTrace, (jlong)0)); + } else { + JniLocalRef msg(env.NewStringUTF("No java exception or message provided.")); + ex = static_cast(env.NewObject( + NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, + (jstring)msg, (jstring) nullptr, (jlong)0)); + } + env.Throw(ex); } void NativeScriptException::Init() { - JEnv env; + JEnv env; - RUNTIME_CLASS = env.FindClass("com/tns/Runtime"); - assert(RUNTIME_CLASS != nullptr); + RUNTIME_CLASS = env.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); - THROWABLE_CLASS = env.FindClass("java/lang/Throwable"); - assert(THROWABLE_CLASS != nullptr); + THROWABLE_CLASS = env.FindClass("java/lang/Throwable"); + assert(THROWABLE_CLASS != nullptr); - NATIVESCRIPTEXCEPTION_CLASS = env.FindClass("com/tns/NativeScriptException"); - assert(NATIVESCRIPTEXCEPTION_CLASS != nullptr); + NATIVESCRIPTEXCEPTION_CLASS = env.FindClass("com/tns/NativeScriptException"); + assert(NATIVESCRIPTEXCEPTION_CLASS != nullptr); - NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID = env.GetMethodID(NATIVESCRIPTEXCEPTION_CLASS, "", "(Ljava/lang/String;Ljava/lang/String;J)V"); - assert(NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID != nullptr); + NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID = + env.GetMethodID(NATIVESCRIPTEXCEPTION_CLASS, "", + "(Ljava/lang/String;Ljava/lang/String;J)V"); + assert(NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID != nullptr); - NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID = env.GetMethodID(NATIVESCRIPTEXCEPTION_CLASS, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V"); - assert(NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID != nullptr); + NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID = env.GetMethodID( + NATIVESCRIPTEXCEPTION_CLASS, "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V"); + assert(NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID != nullptr); - NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID = env.GetStaticMethodID(NATIVESCRIPTEXCEPTION_CLASS, "getStackTraceAsString", "(Ljava/lang/Throwable;)Ljava/lang/String;"); - assert(NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID != nullptr); + NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID = + env.GetStaticMethodID(NATIVESCRIPTEXCEPTION_CLASS, + "getStackTraceAsString", + "(Ljava/lang/Throwable;)Ljava/lang/String;"); + assert(NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID != nullptr); - NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID = env.GetStaticMethodID(NATIVESCRIPTEXCEPTION_CLASS, "getMessage", "(Ljava/lang/Throwable;)Ljava/lang/String;"); - assert(NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID != nullptr); + NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID = + env.GetStaticMethodID(NATIVESCRIPTEXCEPTION_CLASS, "getMessage", + "(Ljava/lang/Throwable;)Ljava/lang/String;"); + assert(NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID != nullptr); } std::string NativeScriptException::ToString() const { - std::stringstream ss; - if (!m_javaException.IsNull()) { - JEnv env; - std::string message = GetExceptionMessage(env, m_javaException); - std::string stackTrace = GetExceptionStackTrace(env, m_javaException); - ss << "Java Exception: " << message << "\n" << stackTrace; - } else if (m_javascriptException != nullptr) { - ss << "JavaScript Exception: " << m_message << "\n" << m_stackTrace; - } else if (!m_message.empty()) { - ss << "Exception Message: " << m_message << "\n" << m_stackTrace; - } else { - ss << "No exception information available."; - } - return ss.str(); + std::stringstream ss; + if (!m_javaException.IsNull()) { + JEnv env; + std::string message = GetExceptionMessage(env, m_javaException); + std::string stackTrace = GetExceptionStackTrace(env, m_javaException); + ss << "Java Exception: " << message << "\n" << stackTrace; + } else if (m_javascriptException != nullptr) { + ss << "JavaScript Exception: " << m_message << "\n" << m_stackTrace; + } else if (!m_message.empty()) { + ss << "Exception Message: " << m_message << "\n" << m_stackTrace; + } else { + ss << "No exception information available."; + } + return ss.str(); } std::string NativeScriptException::GetErrorMessage() const { - if(!m_javaException.IsNull()) { - JEnv env; - return GetExceptionMessage(env, m_javaException); - } else if (m_javascriptException != nullptr) { - return m_message; - } else { - return m_message.empty() ? "No exception message available." : m_message; - } + if (!m_javaException.IsNull()) { + JEnv env; + return GetExceptionMessage(env, m_javaException); + } else if (m_javascriptException != nullptr) { + return m_message; + } else { + return m_message.empty() ? "No exception message available." : m_message; + } } -// ON V8 UNCAUGHT EXCEPTION -void NativeScriptException::OnUncaughtError(Local message, Local error) { - string errorMessage = GetErrorMessage(message, error); - string stackTrace = GetErrorStackTrace(message->GetStackTrace()); - - NativeScriptException e(errorMessage, stackTrace); - e.ReThrowToJava(); +const char* NativeScriptException::what() const noexcept { + m_whatCache = ToString(); + return m_whatCache.c_str(); } -void NativeScriptException::CallJsFuncWithErr(Local errObj, jboolean isDiscarded) { - auto isolate = Isolate::GetCurrent(); - HandleScope scope(isolate); - - auto context = isolate->GetCurrentContext(); - auto globalHandle = context->Global(); - - Local handler; - if (isDiscarded) { - globalHandle->Get(context, V8StringConstants::GetDiscardedError(isolate)).ToLocal(&handler); - } else { - globalHandle->Get(context, V8StringConstants::GetUncaughtError(isolate)).ToLocal(&handler); - } - auto isEmpty = handler.IsEmpty(); - auto isFunction = handler->IsFunction(); +// ON V8 UNCAUGHT EXCEPTION +void NativeScriptException::OnUncaughtError(Local message, + Local error) { + string errorMessage = GetErrorMessage(message, error); + string stackTrace = GetErrorStackTrace(message->GetStackTrace()); - if (!isEmpty && isFunction) { - auto thiz = Object::New(isolate); - auto func = handler.As(); + NativeScriptException e(errorMessage, stackTrace); + e.ReThrowToJava(); +} - func->Call(context, thiz, 1, &errObj); - } +void NativeScriptException::CallJsFuncWithErr(Local errObj, + jboolean isDiscarded) { + auto isolate = Isolate::GetCurrent(); + HandleScope scope(isolate); + + auto context = isolate->GetCurrentContext(); + auto globalHandle = context->Global(); + + Local handler; + if (isDiscarded) { + globalHandle->Get(context, V8StringConstants::GetDiscardedError(isolate)) + .ToLocal(&handler); + } else { + globalHandle->Get(context, V8StringConstants::GetUncaughtError(isolate)) + .ToLocal(&handler); + } + auto isEmpty = handler.IsEmpty(); + auto isFunction = handler->IsFunction(); + + if (!isEmpty && isFunction) { + auto thiz = Object::New(isolate); + auto func = handler.As(); + + func->Call(context, thiz, 1, &errObj); + } } Local NativeScriptException::WrapJavaToJsException() { - Local errObj; + Local errObj; - JEnv env; + JEnv env; - auto isolate = Isolate::GetCurrent(); + auto isolate = Isolate::GetCurrent(); - string excClassName = ObjectManager::GetClassName((jobject) m_javaException); - if (excClassName == "com/tns/NativeScriptException") { - jfieldID fieldID = env.GetFieldID(env.GetObjectClass(m_javaException), "jsValueAddress", "J"); - jlong addr = env.GetLongField(m_javaException, fieldID); - - if (addr != 0) { - auto pv = (Persistent*) addr; - errObj = Local::New(isolate, *pv); - pv->Reset(); - } else { - errObj = GetJavaExceptionFromEnv(m_javaException, env); - } + string excClassName = ObjectManager::GetClassName((jobject)m_javaException); + if (excClassName == "com/tns/NativeScriptException") { + jfieldID fieldID = env.GetFieldID(env.GetObjectClass(m_javaException), + "jsValueAddress", "J"); + jlong addr = env.GetLongField(m_javaException, fieldID); + + if (addr != 0) { + auto pv = (Persistent*)addr; + errObj = Local::New(isolate, *pv); + pv->Reset(); } else { - errObj = GetJavaExceptionFromEnv(m_javaException, env); + errObj = GetJavaExceptionFromEnv(m_javaException, env); } + } else { + errObj = GetJavaExceptionFromEnv(m_javaException, env); + } - return errObj; + return errObj; } -Local NativeScriptException::GetJavaExceptionFromEnv(const JniLocalRef& exc, JEnv& env) { - auto errMsg = GetExceptionMessage(env, exc); - auto stackTrace = GetExceptionStackTrace(env, exc); - DEBUG_WRITE("Error during java interop errorMessage: %s\n stackTrace:\n %s", errMsg.c_str(), stackTrace.c_str()); - - auto isolate = Isolate::GetCurrent(); - auto objectManager = Runtime::GetObjectManager(isolate); - - auto msg = ArgConverter::ConvertToV8String(isolate, errMsg); - auto errObj = Exception::Error(msg).As(); - - jint javaObjectID = objectManager->GetOrCreateObjectId((jobject) exc); - auto nativeExceptionObject = objectManager->GetJsObjectByJavaObject(javaObjectID); - - if (nativeExceptionObject.IsEmpty()) { - string className = objectManager->GetClassName((jobject) exc); - nativeExceptionObject = objectManager->CreateJSWrapper(javaObjectID, className); - } - - auto context = isolate->GetCurrentContext(); - errObj->Set(context, V8StringConstants::GetNativeException(isolate), nativeExceptionObject); - - string jsStackTraceMessage = GetErrorStackTrace(Exception::GetStackTrace(errObj)); - errObj->Set(context, V8StringConstants::GetStack(isolate), ArgConverter::ConvertToV8String(isolate, jsStackTraceMessage)); - errObj->Set(context, V8StringConstants::GetStackTrace(isolate), ArgConverter::ConvertToV8String(isolate, jsStackTraceMessage + stackTrace)); - - return errObj; +Local NativeScriptException::GetJavaExceptionFromEnv( + const JniLocalRef& exc, JEnv& env) { + auto errMsg = GetExceptionMessage(env, exc); + auto stackTrace = GetExceptionStackTrace(env, exc); + DEBUG_WRITE("Error during java interop errorMessage: %s\n stackTrace:\n %s", + errMsg.c_str(), stackTrace.c_str()); + + auto isolate = Isolate::GetCurrent(); + auto objectManager = Runtime::GetObjectManager(isolate); + + auto msg = ArgConverter::ConvertToV8String(isolate, errMsg); + auto errObj = Exception::Error(msg).As(); + + jint javaObjectID = objectManager->GetOrCreateObjectId((jobject)exc); + auto nativeExceptionObject = + objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (nativeExceptionObject.IsEmpty()) { + string className = objectManager->GetClassName((jobject)exc); + nativeExceptionObject = + objectManager->CreateJSWrapper(javaObjectID, className); + } + + auto context = isolate->GetCurrentContext(); + errObj->Set(context, V8StringConstants::GetNativeException(isolate), + nativeExceptionObject); + + string jsStackTraceMessage = + GetErrorStackTrace(Exception::GetStackTrace(errObj)); + errObj->Set(context, V8StringConstants::GetStack(isolate), + ArgConverter::ConvertToV8String(isolate, jsStackTraceMessage)); + errObj->Set(context, V8StringConstants::GetStackTrace(isolate), + ArgConverter::ConvertToV8String( + isolate, jsStackTraceMessage + stackTrace)); + + return errObj; } -string NativeScriptException::GetFullMessage(const TryCatch& tc, const string& jsExceptionMessage) { - auto ex = tc.Exception(); +string NativeScriptException::GetFullMessage(const TryCatch& tc, + const string& jsExceptionMessage) { + auto ex = tc.Exception(); - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - v8::Local context = isolate->GetEnteredOrMicrotaskContext(); + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Local context = isolate->GetEnteredOrMicrotaskContext(); - auto message = tc.Message(); + auto message = tc.Message(); - stringstream ss; - ss << jsExceptionMessage; + stringstream ss; + ss << jsExceptionMessage; - //get script name - auto scriptResName = message->GetScriptResourceName(); + // get script name + auto scriptResName = message->GetScriptResourceName(); - //get stack trace - string stackTraceMessage = GetErrorStackTrace(message->GetStackTrace()); + // get stack trace + string stackTraceMessage = GetErrorStackTrace(message->GetStackTrace()); - if (!scriptResName.IsEmpty() && scriptResName->IsString()) { - ss << endl <<"File: (" << ArgConverter::ConvertToString(scriptResName.As()); - } else { - ss << endl <<"File: ("; - } - ss << ":" << message->GetLineNumber(context).ToChecked() << ":" << message->GetStartColumn() << ")" << endl << endl; - ss << "StackTrace: " << endl << stackTraceMessage << endl; + if (!scriptResName.IsEmpty() && scriptResName->IsString()) { + ss << endl + << "File: (" + << ArgConverter::ConvertToString(scriptResName.As()); + } else { + ss << endl << "File: ("; + } + ss << ":" << message->GetLineNumber(context).ToChecked() << ":" + << message->GetStartColumn() << ")" << endl + << endl; + ss << "StackTrace: " << endl << stackTraceMessage << endl; - string loggedMessage = ss.str(); + string loggedMessage = ss.str(); - PrintErrorMessage(loggedMessage); + PrintErrorMessage(loggedMessage); - if (!tc.CanContinue()) { - stringstream errM; - errM << endl << "An uncaught error has occurred and V8's TryCatch block CAN'T be continued. "; - loggedMessage = errM.str() + loggedMessage; - } + if (!tc.CanContinue()) { + stringstream errM; + errM << endl + << "An uncaught error has occurred and V8's TryCatch block CAN'T be " + "continued. "; + loggedMessage = errM.str() + loggedMessage; + } - return loggedMessage; + return loggedMessage; } -JniLocalRef NativeScriptException::TryGetJavaThrowableObject(JEnv& env, const Local& jsObj) { - JniLocalRef javaThrowableObject; +JniLocalRef NativeScriptException::TryGetJavaThrowableObject( + JEnv& env, const Local& jsObj) { + JniLocalRef javaThrowableObject; - auto isolate = Isolate::GetCurrent(); - auto objectManager = Runtime::GetObjectManager(isolate); + auto isolate = Isolate::GetCurrent(); + auto objectManager = Runtime::GetObjectManager(isolate); - auto javaObj = objectManager->GetJavaObjectByJsObject(jsObj); - JniLocalRef objClass; + auto javaObj = objectManager->GetJavaObjectByJsObject(jsObj); + JniLocalRef objClass; - if (!javaObj.IsNull()) { - objClass = JniLocalRef(env.GetObjectClass(javaObj)); - } else { - auto isolate = jsObj->GetIsolate(); - auto context = isolate->GetCurrentContext(); - Local nativeEx; - jsObj->Get(context, V8StringConstants::GetNativeException(isolate)).ToLocal(&nativeEx); - if (!nativeEx.IsEmpty() && nativeEx->IsObject()) { - javaObj = objectManager->GetJavaObjectByJsObject(nativeEx.As()); - objClass = JniLocalRef(env.GetObjectClass(javaObj)); - } + if (!javaObj.IsNull()) { + objClass = JniLocalRef(env.GetObjectClass(javaObj)); + } else { + auto isolate = jsObj->GetIsolate(); + auto context = isolate->GetCurrentContext(); + Local nativeEx; + jsObj->Get(context, V8StringConstants::GetNativeException(isolate)) + .ToLocal(&nativeEx); + if (!nativeEx.IsEmpty() && nativeEx->IsObject()) { + javaObj = objectManager->GetJavaObjectByJsObject(nativeEx.As()); + objClass = JniLocalRef(env.GetObjectClass(javaObj)); } + } - auto isThrowable = !objClass.IsNull() ? env.IsAssignableFrom(objClass, THROWABLE_CLASS) : JNI_FALSE; + auto isThrowable = !objClass.IsNull() + ? env.IsAssignableFrom(objClass, THROWABLE_CLASS) + : JNI_FALSE; - if (isThrowable == JNI_TRUE) { - javaThrowableObject = JniLocalRef(env.NewLocalRef(javaObj)); - } + if (isThrowable == JNI_TRUE) { + javaThrowableObject = JniLocalRef(env.NewLocalRef(javaObj)); + } - return javaThrowableObject; + return javaThrowableObject; } void NativeScriptException::PrintErrorMessage(const string& errorMessage) { - // split the message by new lines to workaround the LogCat's maximum characters in a single message - stringstream ss(errorMessage); - string line; - while (getline(ss, line, '\n')) { - // TODO: Log in the V8's Console as well? - DEBUG_WRITE("%s", line.c_str()); - } + // split the message by new lines to workaround the LogCat's maximum + // characters in a single message + stringstream ss(errorMessage); + string line; + while (getline(ss, line, '\n')) { + // TODO: Log in the V8's Console as well? + DEBUG_WRITE("%s", line.c_str()); + } } -string NativeScriptException::GetErrorMessage(const Local& message, Local& error, const string& prependMessage) { - - Local message_text_string = message->Get(); - auto mes = ArgConverter::ConvertToString(message_text_string); - - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - v8::Local context = isolate->GetEnteredOrMicrotaskContext(); - - //get whole error message from previous stack - stringstream ss; - - if (prependMessage != "") { - ss << prependMessage << endl; - } - - string errMessage; - bool hasFullErrorMessage = false; - auto v8FullMessage = ArgConverter::ConvertToV8String(isolate, "fullMessage"); - if (error->IsObject() && error.As()->Has(context, v8FullMessage).ToChecked()) { - hasFullErrorMessage = true; - Local errMsgVal; - error.As()->Get(context, v8FullMessage).ToLocal(&errMsgVal); - if (!errMsgVal.IsEmpty()) { - errMessage = ArgConverter::ConvertToString(errMsgVal.As()); - } else { - errMessage = ""; - } - ss << errMessage; +string NativeScriptException::GetErrorMessage(const Local& message, + Local& error, + const string& prependMessage) { + Local message_text_string = message->Get(); + auto mes = ArgConverter::ConvertToString(message_text_string); + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Local context = isolate->GetEnteredOrMicrotaskContext(); + + // get whole error message from previous stack + stringstream ss; + + if (prependMessage != "") { + ss << prependMessage << endl; + } + + string errMessage; + bool hasFullErrorMessage = false; + auto v8FullMessage = ArgConverter::ConvertToV8String(isolate, "fullMessage"); + if (error->IsObject() && + error.As()->Has(context, v8FullMessage).ToChecked()) { + hasFullErrorMessage = true; + Local errMsgVal; + error.As()->Get(context, v8FullMessage).ToLocal(&errMsgVal); + if (!errMsgVal.IsEmpty()) { + errMessage = ArgConverter::ConvertToString(errMsgVal.As()); + } else { + errMessage = ""; } - - auto str = error->ToDetailString(context); - if (!str.IsEmpty()) { - String::Utf8Value utfError(isolate, str.FromMaybe(Local())); - if(hasFullErrorMessage) { - ss << endl; - } - ss << *utfError; + ss << errMessage; + } + + auto str = error->ToDetailString(context); + if (!str.IsEmpty()) { + String::Utf8Value utfError(isolate, str.FromMaybe(Local())); + if (hasFullErrorMessage) { + ss << endl; } + ss << *utfError; + } - return ss.str(); + return ss.str(); } -string NativeScriptException::GetErrorStackTrace(const Local& stackTrace) { - stringstream ss; +string NativeScriptException::GetErrorStackTrace( + const Local& stackTrace) { + stringstream ss; - auto isolate = Isolate::GetCurrent(); - HandleScope handleScope(isolate); + auto isolate = Isolate::GetCurrent(); + HandleScope handleScope(isolate); - int frameCount = stackTrace->GetFrameCount(); + int frameCount = stackTrace->GetFrameCount(); - for (int i = 0; i < frameCount; i++) { - auto frame = stackTrace->GetFrame(isolate, i); - auto funcName = ArgConverter::ConvertToString(frame->GetFunctionName()); - auto srcName = ArgConverter::ConvertToString(frame->GetScriptName()); - auto lineNumber = frame->GetLineNumber(); - auto column = frame->GetColumn(); + for (int i = 0; i < frameCount; i++) { + auto frame = stackTrace->GetFrame(isolate, i); + auto funcName = ArgConverter::ConvertToString(frame->GetFunctionName()); + auto srcName = ArgConverter::ConvertToString(frame->GetScriptName()); + auto lineNumber = frame->GetLineNumber(); + auto column = frame->GetColumn(); - auto startString = i == 0 ? "" : "\t"; + auto startString = i == 0 ? "" : "\t"; - ss << startString << (i > 0 ? "at " : "") << funcName.c_str() << "(" << srcName.c_str() << ":" << lineNumber << ":" << column << ")" << endl; - } + ss << startString << (i > 0 ? "at " : "") << funcName.c_str() << "(" + << srcName.c_str() << ":" << lineNumber << ":" << column << ")" << endl; + } - return ss.str(); + return ss.str(); } -string NativeScriptException::GetExceptionMessage(JEnv& env, jthrowable exception) const { - string errMsg; - JniLocalRef msg(env.CallStaticObjectMethod(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID, exception)); +string NativeScriptException::GetExceptionMessage(JEnv& env, + jthrowable exception) const { + string errMsg; + JniLocalRef msg(env.CallStaticObjectMethod( + NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID, + exception)); - const char* msgStr = env.GetStringUTFChars(msg, nullptr); + const char* msgStr = env.GetStringUTFChars(msg, nullptr); - errMsg.append(msgStr); + errMsg.append(msgStr); - env.ReleaseStringUTFChars(msg, msgStr); + env.ReleaseStringUTFChars(msg, msgStr); - return errMsg; + return errMsg; } -string NativeScriptException::GetExceptionStackTrace(JEnv& env, jthrowable exception) const { - string errStackTrace; - JniLocalRef msg(env.CallStaticObjectMethod(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID, exception)); +string NativeScriptException::GetExceptionStackTrace( + JEnv& env, jthrowable exception) const { + string errStackTrace; + JniLocalRef msg(env.CallStaticObjectMethod( + NATIVESCRIPTEXCEPTION_CLASS, + NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID, exception)); - const char* msgStr = env.GetStringUTFChars(msg, nullptr); + const char* msgStr = env.GetStringUTFChars(msg, nullptr); - errStackTrace.append(msgStr); + errStackTrace.append(msgStr); - env.ReleaseStringUTFChars(msg, msgStr); + env.ReleaseStringUTFChars(msg, msgStr); - return errStackTrace; + return errStackTrace; } jclass NativeScriptException::RUNTIME_CLASS = nullptr; jclass NativeScriptException::THROWABLE_CLASS = nullptr; jclass NativeScriptException::NATIVESCRIPTEXCEPTION_CLASS = nullptr; -jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID = nullptr; -jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID = nullptr; -jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID = nullptr; -jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID = nullptr; \ No newline at end of file +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID = + nullptr; +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID = + nullptr; +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID = + nullptr; +jmethodID NativeScriptException:: + NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID = nullptr; \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/NativeScriptException.h b/test-app/runtime/src/main/cpp/NativeScriptException.h index 049238a30..255fcd5cb 100644 --- a/test-app/runtime/src/main/cpp/NativeScriptException.h +++ b/test-app/runtime/src/main/cpp/NativeScriptException.h @@ -1,110 +1,128 @@ #ifndef NATIVESCRIPTEXCEPTION_H_ #define NATIVESCRIPTEXCEPTION_H_ -#include "v8.h" +#include + #include "JEnv.h" #include "JniLocalRef.h" #include "ObjectManager.h" #include "include/v8.h" +#include "v8.h" namespace tns { -class NativeScriptException { - public: - /* - * Generates a NativeScriptException with java error from environment - */ - NativeScriptException(JEnv& env); - - /* - * Generates a NativeScriptException with given message - */ - NativeScriptException(const std::string& message); - - /* - * Generates a NativeScriptException with given message and stackTrace - */ - NativeScriptException(const std::string& message, const std::string& stackTrace); - - /* - * Generates a NativeScriptException with javascript error from TryCatch and a prepend message if any - */ - NativeScriptException(v8::TryCatch& tc, const std::string& message = ""); - - void ReThrowToV8(); - void ReThrowToJava(); - - std::string ToString() const; - std::string GetErrorMessage() const; - - static void Init(); - - /* - * This handler is attached to v8 to handle uncaught javascript exceptions. - */ - static void OnUncaughtError(v8::Local message, v8::Local error); - - /* - * Calls the global "__onUncaughtError" or "__onDiscardedError" if such is provided - */ - static void CallJsFuncWithErr(v8::Local errObj, jboolean isDiscarded); - - private: - /* - * Try to get native exception or NativeScriptException from js object - */ - JniLocalRef TryGetJavaThrowableObject(JEnv& env, const v8::Local& jsObj); - - /* - * Gets java exception message from jthrowable - */ - std::string GetExceptionMessage(JEnv& env, jthrowable exception) const; - - /* - * Gets java exception stack trace from jthrowable - */ - std::string GetExceptionStackTrace(JEnv& env, jthrowable exception) const; - - /* - * Gets the member m_javaException, wraps it and creates a javascript error object from it - */ - v8::Local WrapJavaToJsException(); - - /* - * Gets all the information from a java exception and puts it in a javascript errror object - */ - v8::Local GetJavaExceptionFromEnv(const JniLocalRef& exc, JEnv& env); - - /* - * Gets all the information from a js message and an js error object and puts it in a string - */ - static std::string GetErrorMessage(const v8::Local& message, v8::Local& error, const std::string& prependMessage = ""); - - /* - * Generates string stack trace from js StackTrace - */ - static std::string GetErrorStackTrace(const v8::Local& stackTrace); - - /* - * Adds a prepend message to the normal message process - */ - std::string GetFullMessage(const v8::TryCatch& tc, const std::string& jsExceptionMessage); - - v8::Persistent* m_javascriptException; - JniLocalRef m_javaException; - std::string m_message; - std::string m_stackTrace; - std::string m_fullMessage; - - static jclass RUNTIME_CLASS; - static jclass THROWABLE_CLASS; - static jclass NATIVESCRIPTEXCEPTION_CLASS; - static jmethodID NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID; - static jmethodID NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID; - static jmethodID NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID; - static jmethodID NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID; - - static void PrintErrorMessage(const std::string& errorMessage); +class NativeScriptException : public std::exception { + public: + /* + * Generates a NativeScriptException with java error from environment + */ + NativeScriptException(JEnv& env); + + /* + * Generates a NativeScriptException with given message + */ + NativeScriptException(const std::string& message); + + /* + * Generates a NativeScriptException with given message and stackTrace + */ + NativeScriptException(const std::string& message, + const std::string& stackTrace); + + /* + * Generates a NativeScriptException with javascript error from TryCatch and a + * prepend message if any + */ + NativeScriptException(v8::TryCatch& tc, const std::string& message = ""); + + void ReThrowToV8(); + void ReThrowToJava(); + + std::string ToString() const; + std::string GetErrorMessage() const; + const char* what() const noexcept override; + + static void Init(); + + /* + * This handler is attached to v8 to handle uncaught javascript exceptions. + */ + static void OnUncaughtError(v8::Local message, + v8::Local error); + + /* + * Calls the global "__onUncaughtError" or "__onDiscardedError" if such is + * provided + */ + static void CallJsFuncWithErr(v8::Local errObj, + jboolean isDiscarded); + + private: + /* + * Try to get native exception or NativeScriptException from js object + */ + JniLocalRef TryGetJavaThrowableObject(JEnv& env, + const v8::Local& jsObj); + + /* + * Gets java exception message from jthrowable + */ + std::string GetExceptionMessage(JEnv& env, jthrowable exception) const; + + /* + * Gets java exception stack trace from jthrowable + */ + std::string GetExceptionStackTrace(JEnv& env, jthrowable exception) const; + + /* + * Gets the member m_javaException, wraps it and creates a javascript error + * object from it + */ + v8::Local WrapJavaToJsException(); + + /* + * Gets all the information from a java exception and puts it in a javascript + * errror object + */ + v8::Local GetJavaExceptionFromEnv(const JniLocalRef& exc, + JEnv& env); + + /* + * Gets all the information from a js message and an js error object and puts + * it in a string + */ + static std::string GetErrorMessage(const v8::Local& message, + v8::Local& error, + const std::string& prependMessage = ""); + + /* + * Generates string stack trace from js StackTrace + */ + static std::string GetErrorStackTrace( + const v8::Local& stackTrace); + + /* + * Adds a prepend message to the normal message process + */ + std::string GetFullMessage(const v8::TryCatch& tc, + const std::string& jsExceptionMessage); + + v8::Persistent* m_javascriptException; + JniLocalRef m_javaException; + std::string m_message; + std::string m_stackTrace; + std::string m_fullMessage; + mutable std::string m_whatCache; + + static jclass RUNTIME_CLASS; + static jclass THROWABLE_CLASS; + static jclass NATIVESCRIPTEXCEPTION_CLASS; + static jmethodID NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID; + static jmethodID NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID; + static jmethodID NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID; + static jmethodID NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID; + + static void PrintErrorMessage(const std::string& errorMessage); }; -} +} // namespace tns #endif /* NATIVESCRIPTEXCEPTION_H_ */ diff --git a/test-app/runtime/src/main/cpp/Runtime.cpp b/test-app/runtime/src/main/cpp/Runtime.cpp index e549beef3..aab1050ec 100644 --- a/test-app/runtime/src/main/cpp/Runtime.cpp +++ b/test-app/runtime/src/main/cpp/Runtime.cpp @@ -72,6 +72,25 @@ void SIG_handler(int sigNumber) { throw NativeScriptException(msg.str()); } +void LogAndAbortUncaught() { + try { + throw; // rethrow the current unknown + } catch (const tns::NativeScriptException& e) { + // We only have message/stack; no safe ReThrowToJava here. + __android_log_print(ANDROID_LOG_FATAL, "TNS.Native", + "Uncaught NativeScriptException: %s", e.what()); + } catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_FATAL, "TNS.Native", + "Uncaught std::exception: %s", e.what()); + } catch (...) { + __android_log_print(ANDROID_LOG_FATAL, "TNS.Native", + "Uncaught unknown native exception"); + } + + // Preserve default abort behavior so crashes are visible to tooling + std::_Exit(EXIT_FAILURE); +} + void Runtime::Init(JavaVM* vm, void* reserved) { __android_log_print(ANDROID_LOG_INFO, "TNS.Runtime", "NativeScript Runtime Version %s, commit %s", @@ -92,6 +111,8 @@ void Runtime::Init(JavaVM* vm, void* reserved) { sigaction(SIGABRT, &action, NULL); sigaction(SIGSEGV, &action, NULL); } + // Set terminate handler for uncaught exceptions + std::set_terminate(LogAndAbortUncaught); } int Runtime::GetAndroidVersion() {