Skip to content

Commit 8b5ca4a

Browse files
authored
Merge pull request #1399 from NativeScript/trifonov/exception-reporting
refactoring message and stack trace reporting
2 parents b1f87d0 + cc95ba5 commit 8b5ca4a

File tree

10 files changed

+141
-76
lines changed

10 files changed

+141
-76
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@
1212
- [java.lang.NullPointerException in Metadata generator(#13795)](https://github.com/NativeScript/android-runtime/issues/1379)
1313
- [Buffer() is deprecated(#1392)](https://github.com/NativeScript/android-runtime/pull/1392)
1414
- [Warnings when building android(#1396)](https://github.com/NativeScript/android-runtime/issues/1396)
15+
- [No JS stack on discardedError and unhandledError(#1354)](https://github.com/NativeScript/android-runtime/issues/1354)
16+
17+
## Breaking Changes
18+
19+
- Exception information in onDiscarderError and onUnhandledError is changed so that `message` contains the exception message and `stackTrace` contains only the stackTrace. In the previous implementation `stackTrace` contained some additional details (including the exception message) and the `message` was something like:
20+
21+
```
22+
The application crashed because of an uncaught exception. You can look at "stackTrace" or "nativeException" for more detailed information about the exception.
23+
```
1524
1625
5.4.0
1726
==

test-app/app/src/main/assets/app/tests/discardedExceptionsTest.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ describe("Tests discarded exception ", function () {
1818
expect(reportedException).not.toBe(null);
1919
expect(reportedException.nativeException).not.toBe(null);
2020
expect(reportedException.nativeException.getMessage()).toBe('Exception to suppress');
21-
expect(reportedException.stackTrace).toContain('Error on "main" thread for reportSupressedException');
21+
expect(reportedException.message).toBe('Exception to suppress');
22+
expect(reportedException.stackTrace).toContain('com.tns.tests.DiscardedExceptionTest.reportSupressedException');
2223
});
2324

2425
afterEach(function() {

test-app/app/src/main/java/com/tns/NativeScriptUncaughtExceptionHandler.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.tns;
22

33
import java.lang.Thread.UncaughtExceptionHandler;
4-
54
import android.content.Context;
65

76
public class NativeScriptUncaughtExceptionHandler implements UncaughtExceptionHandler {
@@ -19,18 +18,19 @@ public NativeScriptUncaughtExceptionHandler(Logger logger, Context context) {
1918

2019
@Override
2120
public void uncaughtException(Thread thread, Throwable ex) {
22-
String currentThreadMessage = "An uncaught Exception occurred on \"" + thread.getName() + "\" thread.\n";
23-
24-
String errorMessage = currentThreadMessage + Runtime.getStackTraceErrorMessage(ex);
21+
String currentThreadMessage = String.format("An uncaught Exception occurred on \"%s\" thread.\n%s\n", thread.getName(), ex.getMessage());
22+
String stackTraceErrorMessage = Runtime.getStackTraceErrorMessage(ex);
23+
String errorMessage = currentThreadMessage + stackTraceErrorMessage;
2524

2625
if (Runtime.isInitialized()) {
2726
try {
28-
ex.printStackTrace();
27+
// print this only in debug
28+
System.err.println(errorMessage);
2929

3030
Runtime runtime = Runtime.getCurrentRuntime();
3131

3232
if (runtime != null) {
33-
runtime.passUncaughtExceptionToJs(ex, errorMessage);
33+
runtime.passUncaughtExceptionToJs(ex, ex.getMessage(), stackTraceErrorMessage);
3434
}
3535
} catch (Throwable t) {
3636
t.printStackTrace();

test-app/runtime/src/main/cpp/NativeScriptException.cpp

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,20 @@ NativeScriptException::NativeScriptException(const string& message)
2323
m_javascriptException(nullptr), m_javaException(JniLocalRef()), m_message(message) {
2424
}
2525

26+
NativeScriptException::NativeScriptException(const string& message, const string& stackTrace)
27+
:
28+
m_javascriptException(nullptr), m_javaException(JniLocalRef()), m_message(message), m_stackTrace(stackTrace) {
29+
}
30+
2631
NativeScriptException::NativeScriptException(TryCatch& tc, const string& message)
2732
:
2833
m_javaException(JniLocalRef()) {
2934
auto isolate = Isolate::GetCurrent();
3035
m_javascriptException = new Persistent<Value>(isolate, tc.Exception());
31-
bool isMessageEmpty = tc.Message().IsEmpty();
32-
bool isExceptionEmpty = tc.Exception().IsEmpty();
33-
m_message = GetFullMessage(tc, isExceptionEmpty, isMessageEmpty, message);
36+
auto ex = tc.Exception();
37+
m_message = GetErrorMessage(tc.Message(), ex, message);
38+
m_stackTrace = GetErrorStackTrace(tc.Message()->GetStackTrace());
39+
m_fullMessage = GetFullMessage(tc, m_message);
3440
tc.Reset();
3541
}
3642

@@ -40,9 +46,15 @@ void NativeScriptException::ReThrowToV8() {
4046

4147
if (m_javascriptException != nullptr) {
4248
errObj = Local<Value>::New(isolate, *m_javascriptException);
43-
if (errObj->IsObject() && !m_message.empty()) {
44-
errObj.As<Object>()->Set(ArgConverter::ConvertToV8String(isolate, "fullMessage"), ArgConverter::ConvertToV8String(isolate, m_message));
49+
if (errObj->IsObject()) {
50+
if (!m_fullMessage.empty()) {
51+
errObj.As<Object>()->Set(ArgConverter::ConvertToV8String(isolate, "fullMessage"), ArgConverter::ConvertToV8String(isolate, m_fullMessage));
52+
} else if (!m_message.empty()) {
53+
errObj.As<Object>()->Set(ArgConverter::ConvertToV8String(isolate, "fullMessage"), ArgConverter::ConvertToV8String(isolate, m_message));
54+
}
4555
}
56+
} else if (!m_fullMessage.empty()) {
57+
errObj = Exception::Error(ArgConverter::ConvertToV8String(isolate, m_fullMessage));
4658
} else if (!m_message.empty()) {
4759
errObj = Exception::Error(ArgConverter::ConvertToV8String(isolate, m_message));
4860
} else if (!m_javaException.IsNull()) {
@@ -83,22 +95,24 @@ void NativeScriptException::ReThrowToJava() {
8395
ex = (jthrowable) exObj.Move();
8496
}
8597

98+
JniLocalRef msg(env.NewStringUTF(m_message.c_str()));
99+
JniLocalRef stackTrace(env.NewStringUTF(m_stackTrace.c_str()));
100+
86101
if (ex == nullptr) {
87-
JniLocalRef msg(env.NewStringUTF(m_message.c_str()));
88-
ex = static_cast<jthrowable>(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring) msg, reinterpret_cast<jlong>(m_javascriptException)));
102+
ex = static_cast<jthrowable>(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring) msg, (jstring)stackTrace, reinterpret_cast<jlong>(m_javascriptException)));
89103
} else {
90104
auto excClassName = objectManager->GetClassName(ex);
91105
if (excClassName != "com/tns/NativeScriptException") {
92-
JniLocalRef msg(env.NewStringUTF(m_message.c_str()));
93-
ex = static_cast<jthrowable>(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID, (jstring) msg, ex));
106+
ex = static_cast<jthrowable>(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID, (jstring) msg, (jstring)stackTrace, ex));
94107
}
95108
}
96109
} else if (!m_message.empty()) {
97110
JniLocalRef msg(env.NewStringUTF(m_message.c_str()));
98-
ex = static_cast<jthrowable>(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring) msg, (jlong) 0));
111+
JniLocalRef stackTrace(env.NewStringUTF(m_stackTrace.c_str()));
112+
ex = static_cast<jthrowable>(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring) msg, (jstring)stackTrace, (jlong) 0));
99113
} else {
100114
JniLocalRef msg(env.NewStringUTF("No java exception or message provided."));
101-
ex = static_cast<jthrowable>(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring) msg, (jlong) 0));
115+
ex = static_cast<jthrowable>(env.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring) msg, (jstring) nullptr, (jlong) 0));
102116
}
103117
env.Throw(ex);
104118
}
@@ -115,10 +129,10 @@ void NativeScriptException::Init() {
115129
NATIVESCRIPTEXCEPTION_CLASS = env.FindClass("com/tns/NativeScriptException");
116130
assert(NATIVESCRIPTEXCEPTION_CLASS != nullptr);
117131

118-
NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID = env.GetMethodID(NATIVESCRIPTEXCEPTION_CLASS, "<init>", "(Ljava/lang/String;J)V");
132+
NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID = env.GetMethodID(NATIVESCRIPTEXCEPTION_CLASS, "<init>", "(Ljava/lang/String;Ljava/lang/String;J)V");
119133
assert(NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID != nullptr);
120134

121-
NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID = env.GetMethodID(NATIVESCRIPTEXCEPTION_CLASS, "<init>", "(Ljava/lang/String;Ljava/lang/Throwable;)V");
135+
NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID = env.GetMethodID(NATIVESCRIPTEXCEPTION_CLASS, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V");
122136
assert(NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID != nullptr);
123137

124138
NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID = env.GetStaticMethodID(NATIVESCRIPTEXCEPTION_CLASS, "getStackTraceAsString", "(Ljava/lang/Throwable;)Ljava/lang/String;");
@@ -128,8 +142,9 @@ void NativeScriptException::Init() {
128142
// ON V8 UNCAUGHT EXCEPTION
129143
void NativeScriptException::OnUncaughtError(Local<Message> message, Local<Value> error) {
130144
string errorMessage = GetErrorMessage(message, error);
145+
string stackTrace = GetErrorStackTrace(message->GetStackTrace());
131146

132-
NativeScriptException e(errorMessage);
147+
NativeScriptException e(errorMessage, stackTrace);
133148
e.ReThrowToJava();
134149
}
135150

@@ -203,17 +218,31 @@ Local<Value> NativeScriptException::GetJavaExceptionFromEnv(const JniLocalRef& e
203218
return errObj;
204219
}
205220

206-
string NativeScriptException::GetFullMessage(const TryCatch& tc, bool isExceptionEmpty, bool isMessageEmpty, const string& prependMessage) {
221+
string NativeScriptException::GetFullMessage(const TryCatch& tc, const string& jsExceptionMessage) {
207222
auto ex = tc.Exception();
208223

209-
string jsExeptionMessage;
224+
v8::Isolate* isolate = v8::Isolate::GetCurrent();
225+
v8::Local<v8::Context> context = isolate->GetEnteredContext();
210226

211-
if (!isExceptionEmpty && !isMessageEmpty) {
212-
jsExeptionMessage = GetErrorMessage(tc.Message(), ex);
213-
}
227+
auto message = tc.Message();
214228

215229
stringstream ss;
216-
ss << endl << prependMessage << jsExeptionMessage;
230+
ss << jsExceptionMessage;
231+
232+
//get script name
233+
auto scriptResName = message->GetScriptResourceName();
234+
235+
//get stack trace
236+
string stackTraceMessage = GetErrorStackTrace(message->GetStackTrace());
237+
238+
if (!scriptResName.IsEmpty() && scriptResName->IsString()) {
239+
ss << endl <<"File: \"" << ArgConverter::ConvertToString(scriptResName.As<String>());
240+
} else {
241+
ss << endl <<"File: \"<unknown>";
242+
}
243+
ss << ", line: " << message->GetLineNumber(context).ToChecked() << ", column: " << message->GetStartColumn() << endl << endl;
244+
ss << "StackTrace: " << endl << stackTraceMessage << endl;
245+
217246
string loggedMessage = ss.str();
218247

219248
PrintErrorMessage(loggedMessage);
@@ -266,46 +295,38 @@ void NativeScriptException::PrintErrorMessage(const string& errorMessage) {
266295
}
267296
}
268297

269-
string NativeScriptException::GetErrorMessage(const Local<Message>& message, Local<Value>& error) {
298+
string NativeScriptException::GetErrorMessage(const Local<Message>& message, Local<Value>& error, const string& prependMessage) {
270299

271300
Local<String> message_text_string = message->Get();
272301
auto mes = ArgConverter::ConvertToString(message_text_string);
273302

274303
v8::Isolate* isolate = v8::Isolate::GetCurrent();
275304
v8::Local<v8::Context> context = isolate->GetEnteredContext();
276-
int line_number = message->GetLineNumber(context).FromMaybe(0);
277305

278306
//get whole error message from previous stack
307+
stringstream ss;
308+
309+
if (prependMessage != "") {
310+
ss << prependMessage << endl;
311+
}
312+
279313
string errMessage;
314+
bool hasFullErrorMessage = false;
280315
auto v8FullMessage = ArgConverter::ConvertToV8String(isolate, "fullMessage");
281316
if (error->IsObject() && error.As<Object>()->Has(context, v8FullMessage).ToChecked()) {
317+
hasFullErrorMessage = true;
282318
errMessage = ArgConverter::ConvertToString(error.As<Object>()->Get(v8FullMessage).As<String>());
319+
ss << errMessage;
283320
}
284321

285-
286-
//get current message
287322
auto str = error->ToDetailString(context);
288-
if (str.IsEmpty()) {
289-
str = String::NewFromUtf8(isolate, "");
290-
}
291-
String::Utf8Value utfError(isolate, str.FromMaybe(Local<String>()));
292-
293-
//get script name
294-
auto scriptResName = message->GetScriptResourceName();
295-
296-
//get stack trace
297-
string stackTraceMessage = GetErrorStackTrace(message->GetStackTrace());
298-
299-
stringstream ss;
300-
ss << endl << errMessage;
301-
ss << endl << *utfError << endl;
302-
if (!scriptResName.IsEmpty() && scriptResName->IsString()) {
303-
ss << "File: \"" << ArgConverter::ConvertToString(scriptResName.As<String>());
304-
} else {
305-
ss << "File: \"<unknown>";
323+
if (!str.IsEmpty()) {
324+
String::Utf8Value utfError(isolate, str.FromMaybe(Local<String>()));
325+
if(hasFullErrorMessage) {
326+
ss << endl;
327+
}
328+
ss << *utfError;
306329
}
307-
ss << ", line: " << message->GetLineNumber(context).ToChecked() << ", column: " << message->GetStartColumn() << endl << endl;
308-
ss << "StackTrace: " << endl << stackTraceMessage << endl;
309330

310331
return ss.str();
311332
}

test-app/runtime/src/main/cpp/NativeScriptException.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ class NativeScriptException {
2020
*/
2121
NativeScriptException(const std::string& message);
2222

23+
/*
24+
* Generates a NativeScriptException with given message and stackTrace
25+
*/
26+
NativeScriptException(const std::string& message, const std::string& stackTrace);
27+
2328
/*
2429
* Generates a NativeScriptException with javascript error from TryCatch and a prepend message if any
2530
*/
@@ -64,7 +69,7 @@ class NativeScriptException {
6469
/*
6570
* Gets all the information from a js message and an js error object and puts it in a string
6671
*/
67-
static std::string GetErrorMessage(const v8::Local<v8::Message>& message, v8::Local<v8::Value>& error);
72+
static std::string GetErrorMessage(const v8::Local<v8::Message>& message, v8::Local<v8::Value>& error, const std::string& prependMessage = "");
6873

6974
/*
7075
* Generates string stack trace from js StackTrace
@@ -74,11 +79,13 @@ class NativeScriptException {
7479
/*
7580
* Adds a prepend message to the normal message process
7681
*/
77-
std::string GetFullMessage(const v8::TryCatch& tc, bool isExceptionEmpty, bool isMessageEmpty, const std::string& prependMessage = "");
82+
std::string GetFullMessage(const v8::TryCatch& tc, const std::string& jsExceptionMessage);
7883

7984
v8::Persistent<v8::Value>* m_javascriptException;
8085
JniLocalRef m_javaException;
8186
std::string m_message;
87+
std::string m_stackTrace;
88+
std::string m_fullMessage;
8289

8390
static jclass RUNTIME_CLASS;
8491
static jclass THROWABLE_CLASS;

test-app/runtime/src/main/cpp/Runtime.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -370,12 +370,10 @@ bool Runtime::TryCallGC() {
370370
return success;
371371
}
372372

373-
void Runtime::PassExceptionToJsNative(JNIEnv* env, jobject obj, jthrowable exception, jstring stackTrace, jboolean isDiscarded) {
373+
void Runtime::PassExceptionToJsNative(JNIEnv* env, jobject obj, jthrowable exception, jstring message, jstring stackTrace, jboolean isDiscarded) {
374374
auto isolate = m_isolate;
375375

376-
//create error message
377-
string errMsg = isDiscarded ? "An exception was caught and discarded. You can look at \"stackTrace\" or \"nativeException\" for more detailed information about the exception.":
378-
"The application crashed because of an uncaught exception. You can look at \"stackTrace\" or \"nativeException\" for more detailed information about the exception.";
376+
string errMsg = ArgConverter::jstringToString(message);
379377

380378
auto errObj = Exception::Error(ArgConverter::ConvertToV8String(isolate, errMsg)).As<Object>();
381379

@@ -392,9 +390,6 @@ void Runtime::PassExceptionToJsNative(JNIEnv* env, jobject obj, jthrowable excep
392390
}
393391
}
394392

395-
string stackTraceText = ArgConverter::jstringToString(stackTrace);
396-
errMsg += "\n" + stackTraceText;
397-
398393
//create a JS error object
399394
errObj->Set(V8StringConstants::GetNativeException(isolate), nativeExceptionObject);
400395
errObj->Set(V8StringConstants::GetStackTrace(isolate), ArgConverter::jstringToV8String(isolate, stackTrace));

test-app/runtime/src/main/cpp/Runtime.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class Runtime {
5252
void AdjustAmountOfExternalAllocatedMemory();
5353
bool NotifyGC(JNIEnv* env, jobject obj);
5454
bool TryCallGC();
55-
void PassExceptionToJsNative(JNIEnv* env, jobject obj, jthrowable exception, jstring stackTrace, jboolean isDiscarded);
55+
void PassExceptionToJsNative(JNIEnv* env, jobject obj, jthrowable exception, jstring message, jstring stackTrace, jboolean isDiscarded);
5656
void PassUncaughtExceptionFromWorkerToMainHandler(v8::Local<v8::String> message, v8::Local<v8::String> stackTrace, v8::Local<v8::String> filename, int lineno);
5757
void ClearStartupData(JNIEnv* env, jobject obj);
5858
void DestroyRuntime();

test-app/runtime/src/main/cpp/com_tns_Runtime.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ extern "C" JNIEXPORT void Java_com_tns_Runtime_unlock(JNIEnv* env, jobject obj,
245245
}
246246
}
247247

248-
extern "C" JNIEXPORT void Java_com_tns_Runtime_passExceptionToJsNative(JNIEnv* env, jobject obj, jint runtimeId, jthrowable exception, jstring stackTrace, jboolean isDiscarded) {
248+
extern "C" JNIEXPORT void Java_com_tns_Runtime_passExceptionToJsNative(JNIEnv* env, jobject obj, jint runtimeId, jthrowable exception, jstring message, jstring stackTrace, jboolean isDiscarded) {
249249
auto runtime = TryGetRuntime(runtimeId);
250250
if (runtime == nullptr) {
251251
return;
@@ -256,7 +256,7 @@ extern "C" JNIEXPORT void Java_com_tns_Runtime_passExceptionToJsNative(JNIEnv* e
256256
v8::HandleScope handleScope(isolate);
257257

258258
try {
259-
runtime->PassExceptionToJsNative(env, obj, exception, stackTrace, isDiscarded);
259+
runtime->PassExceptionToJsNative(env, obj, exception, message, stackTrace, isDiscarded);
260260
} catch (NativeScriptException& e) {
261261
e.ReThrowToJava();
262262
} catch (std::exception e) {

test-app/runtime/src/main/java/com/tns/NativeScriptException.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,39 @@
44
public class NativeScriptException extends RuntimeException {
55
@SuppressWarnings("unused")
66
private long jsValueAddress = 0;
7+
private String incomingStackTrace;
78

89
public NativeScriptException() {
910
super();
1011
}
1112

12-
public NativeScriptException(String detailMessage) {
13-
super(detailMessage);
13+
public NativeScriptException(String message) {
14+
super(message);
1415
}
1516

1617
public NativeScriptException(Throwable throwable) {
1718
super(throwable);
1819
}
1920

20-
public NativeScriptException(String detailMessage, Throwable throwable) {
21-
super(detailMessage, throwable);
21+
public NativeScriptException(String message, Throwable throwable) {
22+
super(message, throwable);
2223
}
2324

24-
public NativeScriptException(String detailMessage, long jsValueAddress) {
25-
super(detailMessage);
25+
public NativeScriptException(String message, String stackTrace, Throwable throwable) {
26+
super(message, throwable);
27+
this.incomingStackTrace = stackTrace;
28+
}
29+
30+
public NativeScriptException(String message, String stackTrace, long jsValueAddress) {
31+
super(message);
32+
this.incomingStackTrace = stackTrace;
2633
this.jsValueAddress = jsValueAddress;
2734
}
2835

36+
public String getIncomingStackTrace() {
37+
return incomingStackTrace;
38+
}
39+
2940
@RuntimeCallable
3041
public static String getStackTraceAsString(Throwable ex) {
3142
String errMessage;

0 commit comments

Comments
 (0)