Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove check of throwable type in uncaughtException handler #111

Merged
merged 6 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package backtraceio.library.models;

public class SkipExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.err.println("Exception caught in thread " + t.getName() + ":");
e.printStackTrace();
System.err.println("Exception skipped");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package backtraceio.library.models;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import android.content.Context;

import androidx.test.platform.app.InstrumentationRegistry;

import net.jodah.concurrentunit.Waiter;

import org.junit.Before;
import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import backtraceio.library.BacktraceClient;
import backtraceio.library.BacktraceCredentials;
import backtraceio.library.models.types.BacktraceResultStatus;

public class UncaughtExceptionHandlerTest {

private Context context;
private BacktraceCredentials credentials;

@Before
public void setUp() {
context = InstrumentationRegistry.getInstrumentation().getContext();
credentials = new BacktraceCredentials("https://example-endpoint.com/", "");
}

private static void setRootHandler(Thread.UncaughtExceptionHandler customRootHandler, Thread.UncaughtExceptionHandler newRootHandler) {
try {
Field field = BacktraceExceptionHandler.class.getDeclaredField("rootHandler");
field.setAccessible(true);
field.set(customRootHandler, newRootHandler);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}


private static BacktraceExceptionHandler createBacktraceExceptionHandler(BacktraceClient client) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Constructor<BacktraceExceptionHandler> constructor = BacktraceExceptionHandler.class.getDeclaredConstructor(BacktraceClient.class);
assertTrue(Modifier.isPrivate(constructor.getModifiers()));
constructor.setAccessible(true);
BacktraceExceptionHandler exceptionHandler = constructor.newInstance(client);
setRootHandler(exceptionHandler, new SkipExceptionHandler());
return exceptionHandler;
}

@Test()
public void testUncaughtException() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
// GIVEN
final Waiter waiter = new Waiter();
final Exception exception = new IllegalArgumentException("Test message");
final BacktraceClient client = new BacktraceClient(context, credentials);

final AtomicReference<BacktraceData> testedAtomicReportData = new AtomicReference<>();
client.setOnRequestHandler(data -> {
testedAtomicReportData.set(data);
waiter.resume();
return new BacktraceResult(data.report, data.report.message,
BacktraceResultStatus.Ok);
});

final BacktraceExceptionHandler handler = createBacktraceExceptionHandler(client);

// WHEN
handler.uncaughtException(Thread.currentThread(), exception);

// WAIT FOR THE RESULT FROM ANOTHER THREAD
try {
waiter.await(5, TimeUnit.SECONDS);
} catch (Exception ex) {
fail(ex.getMessage());
}

// THEN
assertNotNull(testedAtomicReportData);
final BacktraceData testedReportData = testedAtomicReportData.get();
assertEquals("Test message", testedReportData.report.exception.getMessage());
assertNull(testedReportData.report.message);
assertTrue(testedReportData.report.diagnosticStack.size() > 0);
assertEquals("java.lang.IllegalArgumentException", testedReportData.report.classifier);
assertEquals("Unhandled Exception", testedReportData.report.attributes.get("error.type"));
assertTrue(testedReportData.report.exceptionTypeReport);
}

@Test
public void testUncaughtError() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
// GIVEN
final Waiter waiter = new Waiter();
final Error error = new OutOfMemoryError();
final BacktraceClient client = new BacktraceClient(context, credentials);

final AtomicReference<BacktraceData> testedAtomicReportData = new AtomicReference<>();
client.setOnRequestHandler(data -> {
testedAtomicReportData.set(data);
waiter.resume();
return new BacktraceResult(data.report, data.report.message,
BacktraceResultStatus.Ok);
});

final BacktraceExceptionHandler handler = createBacktraceExceptionHandler(client);

// WHEN
handler.uncaughtException(Thread.currentThread(), error);

// WAIT FOR THE RESULT FROM ANOTHER THREAD
try {
waiter.await(5, TimeUnit.SECONDS);
} catch (Exception ex) {
fail(ex.getMessage());
}

// THEN
assertNotNull(testedAtomicReportData);
final BacktraceData testedReportData = testedAtomicReportData.get();
assertNull(testedReportData.report.message);
assertTrue(testedReportData.report.diagnosticStack.size() > 0);
assertEquals("java.lang.OutOfMemoryError", testedReportData.report.classifier);
assertEquals("Unhandled Exception", testedReportData.report.attributes.get("error.type"));
assertTrue(testedReportData.report.exceptionTypeReport);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ public class BacktraceDatabase implements Database {

private final String _crashpadHandlerName = "/libcrashpad_handler.so";
private final String _crashpadDatabasePathPrefix = "/crashpad";

private static boolean _timerBackgroundWork = false;
private static Timer _timer;
private transient final String LOG_TAG = BacktraceDatabase.class.getSimpleName();
Expand Down Expand Up @@ -412,6 +411,10 @@ public void delete(BacktraceDatabaseRecord record) {
if (this.backtraceDatabaseContext == null) {
return;
}

if (record == null){
return;
}
this.backtraceDatabaseContext.delete(record);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,8 @@ public void onEvent(BacktraceResult backtraceResult) {
if (record != null) {
record.close();
}
if (backtraceResult != null && backtraceResult.status == BacktraceResultStatus.Ok) {

if (backtraceResult != null && record != null && backtraceResult.status == BacktraceResultStatus.Ok) {
database.delete(record);
BartoszLitwiniuk marked this conversation as resolved.
Show resolved Hide resolved
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package backtraceio.library.models;

import android.os.Looper;

import java.util.Map;
import java.util.concurrent.CountDownLatch;

Expand Down Expand Up @@ -53,23 +51,26 @@ public static void enable(BacktraceClient client) {
public void uncaughtException(final Thread thread, final Throwable throwable) {
OnServerResponseEventListener callback = getCallbackToDefaultHandler(thread, throwable);

if (throwable instanceof Exception) {
BacktraceLogger.e(LOG_TAG, "Sending uncaught exception to Backtrace API", throwable);
BacktraceReport report = new BacktraceReport((Exception) throwable, BacktraceExceptionHandler.customAttributes);
report.attributes.put(BacktraceAttributeConsts.ErrorType, BacktraceAttributeConsts.UnhandledExceptionAttributeType);
this.client.send(report, callback);
BacktraceLogger.d(LOG_TAG, "Uncaught exception sent to Backtrace API");
}
BacktraceLogger.d(LOG_TAG, "Default uncaught exception handler");
BacktraceLogger.e(LOG_TAG, "Sending uncaught exception to Backtrace API", throwable);
BacktraceReport report = new BacktraceReport(this.getCausedException(throwable), BacktraceExceptionHandler.customAttributes);
report.attributes.put(BacktraceAttributeConsts.ErrorType, BacktraceAttributeConsts.UnhandledExceptionAttributeType);
this.client.send(report, callback);
BartoszLitwiniuk marked this conversation as resolved.
Show resolved Hide resolved
BacktraceLogger.d(LOG_TAG, "Uncaught exception sent to Backtrace API");

try {
BacktraceLogger.d(LOG_TAG, "Default uncaught exception handler");
signal.await();
} catch (Exception ex) {
BacktraceLogger.e(LOG_TAG, "Exception during waiting for response", ex);
}
}

private boolean isMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
private Exception getCausedException(Throwable throwable) {
if (throwable instanceof Exception) {
return (Exception) throwable;
}

return new UnhandledThrowableWrapper(throwable);
}

private OnServerResponseEventListener getCallbackToDefaultHandler(final Thread thread, final Throwable throwable) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package backtraceio.library.models;


import androidx.annotation.NonNull;

public class UnhandledThrowableWrapper extends Exception {

private final transient Throwable instance;

public UnhandledThrowableWrapper(Throwable throwable) {
this.instance = throwable;
}

@Override
public String getMessage() {
return this.instance.getMessage();
}

@Override
public String getLocalizedMessage() {
return this.instance.getLocalizedMessage();
}

@Override
public synchronized Throwable getCause() {
return this.instance.getCause();
}

@NonNull
@Override
public StackTraceElement[] getStackTrace() {
return this.instance.getStackTrace();
}

public String getClassifier() {
return this.instance.getClass().getCanonicalName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import backtraceio.library.models.BacktraceData;
import backtraceio.library.models.BacktraceStackFrame;
import backtraceio.library.models.BacktraceStackTrace;
import backtraceio.library.models.UnhandledThrowableWrapper;

/**
* Captured application error
Expand Down Expand Up @@ -182,11 +183,17 @@ public BacktraceReport(
this.diagnosticStack = new BacktraceStackTrace(exception).getStackFrames();

if (this.exceptionTypeReport && exception != null) {
this.classifier = exception.getClass().getCanonicalName();
this.classifier = getExceptionClassifier(exception);
}
this.setDefaultErrorTypeAttribute();
}

public String getExceptionClassifier(Exception exception) {
if (exception instanceof UnhandledThrowableWrapper) {
return ((UnhandledThrowableWrapper) exception).getClassifier();
}
return exception.getClass().getCanonicalName();
}
/**
* To avoid serialization issues with custom exceptions, our goal is to always
* prepare exception in a way potential serialization won't break it
Expand Down
Loading