()
val trace = Stacktrace(stacktrace, projectPackages, logger)
- val errorInternal =
- ErrorInternal(currentEx.javaClass.name, currentEx.localizedMessage, trace)
+ val errorInternal = ErrorInternal(
+ currentEx.javaClass.name,
+ currentEx.localizedMessage,
+ trace
+ )
return@mapTo Error(errorInternal, logger)
}
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Event.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/Event.java
index 4dc9b31e4f..0c560107e5 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Event.java
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Event.java
@@ -51,8 +51,8 @@ private void logNull(String property) {
}
/**
- * The Throwable object that caused the event in your application.
- *
+ * The {@link Throwable} object that caused the event in your application.
+ *
* Manipulating this field does not affect the error information reported to the
* Bugsnag dashboard. Use {@link Event#getErrors()} to access and amend the representation of
* the error that will be sent.
@@ -66,7 +66,7 @@ public Throwable getOriginalError() {
* Information extracted from the {@link Throwable} that caused the event can be found in this
* field. The list contains at least one {@link Error} that represents the thrown object
* with subsequent elements in the list populated from {@link Throwable#getCause()}.
- *
+ *
* A reference to the actual {@link Throwable} object that caused the event is available
* through {@link Event#getOriginalError()} ()}.
*/
@@ -75,6 +75,35 @@ public List getErrors() {
return impl.getErrors();
}
+ /**
+ * Add a new error to this event and return its Error data. The new Error will appear at the
+ * end of the {@link #getErrors() errors list}.
+ */
+ @NonNull
+ public Error addError(@NonNull Throwable error) {
+ return impl.addError(error);
+ }
+
+ /**
+ * Add a new empty {@link ErrorType#ANDROID android} error to this event and return its Error
+ * data. The new Error will appear at the end of the {@link #getErrors() errors list}.
+ */
+ @NonNull
+ public Error addError(@NonNull String errorClass, @Nullable String errorMessage) {
+ return impl.addError(errorClass, errorMessage, ErrorType.ANDROID);
+ }
+
+ /**
+ * Add a new empty error to this event and return its Error data. The new Error will appear
+ * at the end of the {@link #getErrors() errors list}.
+ */
+ @NonNull
+ public Error addError(@NonNull String errorClass,
+ @Nullable String errorMessage,
+ @NonNull ErrorType errorType) {
+ return impl.addError(errorClass, errorMessage, errorType);
+ }
+
/**
* If thread state is being captured along with the event, this field will contain a
* list of {@link Thread} objects.
@@ -84,6 +113,46 @@ public List getThreads() {
return impl.getThreads();
}
+ /**
+ * Create, add and return a new empty {@link Thread} object to this event with a given id
+ * and name. This can be used to augment the event with thread data that would not be picked
+ * up as part of a normal event being generated (for example: native threads managed
+ * by cross-platform toolkits).
+ *
+ * @return a new Thread object of type {@link ErrorType#ANDROID} with no stacktrace
+ */
+ @NonNull
+ public Thread addThread(@NonNull String id,
+ @NonNull String name) {
+ return impl.addThread(
+ id,
+ name,
+ ErrorType.ANDROID,
+ false,
+ Thread.State.RUNNABLE.getDescriptor()
+ );
+ }
+
+ /**
+ * Create, add and return a new empty {@link Thread} object to this event with a given id
+ * and name. This can be used to augment the event with thread data that would not be picked
+ * up as part of a normal event being generated (for example: native threads managed
+ * by cross-platform toolkits).
+ *
+ * @return a new Thread object of type {@link ErrorType#ANDROID} with no stacktrace
+ */
+ @NonNull
+ public Thread addThread(long id,
+ @NonNull String name) {
+ return impl.addThread(
+ Long.toString(id),
+ name,
+ ErrorType.ANDROID,
+ false,
+ Thread.State.RUNNABLE.getDescriptor()
+ );
+ }
+
/**
* A list of breadcrumbs leading up to the event. These values can be accessed and amended
* if necessary. See {@link Breadcrumb} for details of the data available.
@@ -93,6 +162,26 @@ public List getBreadcrumbs() {
return impl.getBreadcrumbs();
}
+ /**
+ * Add a new breadcrumb to this event and return its Breadcrumb object. The new breadcrumb
+ * will be added to the end of the {@link #getBreadcrumbs() breadcrumbs list} by this method.
+ */
+ @NonNull
+ public Breadcrumb leaveBreadcrumb(@NonNull String message,
+ @NonNull BreadcrumbType type,
+ @Nullable Map metadata) {
+ return impl.leaveBreadcrumb(message, type, metadata);
+ }
+
+ /**
+ * Add a new breadcrumb to this event and return its Breadcrumb object. The new breadcrumb
+ * will be added to the end of the {@link #getBreadcrumbs() breadcrumbs list} by this# method.
+ */
+ @NonNull
+ public Breadcrumb leaveBreadcrumb(@NonNull String message) {
+ return impl.leaveBreadcrumb(message, BreadcrumbType.MANUAL, null);
+ }
+
/**
* A list of feature flags active at the time of the event.
* See {@link FeatureFlag} for details of the data available.
@@ -167,7 +256,7 @@ public Severity getSeverity() {
* All events with the same grouping hash will be grouped together into one error. This is an
* advanced usage of the library and mis-using it will cause your events not to group properly
* in your dashboard.
- *
+ *
* As the name implies, this option accepts a hash of sorts.
*/
public void setGroupingHash(@Nullable String groupingHash) {
@@ -179,7 +268,7 @@ public void setGroupingHash(@Nullable String groupingHash) {
* All events with the same grouping hash will be grouped together into one error. This is an
* advanced usage of the library and mis-using it will cause your events not to group properly
* in your dashboard.
- *
+ *
* As the name implies, this option accepts a hash of sorts.
*/
@Nullable
@@ -388,7 +477,7 @@ public void setUnhandled(boolean unhandled) {
* using bugsnag-android-performance, but can also be set manually if required.
*
* @param traceId the ID of the trace the event occurred within
- * @param spanId the ID of the span that the event occurred within
+ * @param spanId the ID of the span that the event occurred within
*/
public void setTraceCorrelation(@NonNull UUID traceId, long spanId) {
if (traceId != null) {
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventInternal.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventInternal.kt
index 88b46df334..8345929248 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventInternal.kt
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventInternal.kt
@@ -6,6 +6,7 @@ import com.bugsnag.android.internal.InternalMetricsNoop
import com.bugsnag.android.internal.JsonHelper
import com.bugsnag.android.internal.TrimMetrics
import java.io.IOException
+import java.util.Date
import java.util.regex.Pattern
internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, MetadataAware, UserAware {
@@ -324,4 +325,72 @@ internal class EventInternal : FeatureFlagAware, JsonStream.Streamable, Metadata
override fun clearFeatureFlag(name: String) = featureFlags.clearFeatureFlag(name)
override fun clearFeatureFlags() = featureFlags.clearFeatureFlags()
+
+ fun addError(thrownError: Throwable?): Error {
+ if (thrownError == null) {
+ val newError = Error(
+ ErrorInternal("null", null, Stacktrace(ArrayList())),
+ logger
+ )
+ errors.add(newError)
+ return newError
+ } else {
+ val newErrors = Error.createError(thrownError, projectPackages, logger)
+ errors.addAll(newErrors)
+ return newErrors.first()
+ }
+ }
+
+ fun addError(errorClass: String?, errorMessage: String?, errorType: ErrorType?): Error {
+ val error = Error(
+ ErrorInternal(
+ errorClass.toString(),
+ errorMessage,
+ Stacktrace(ArrayList()),
+ errorType ?: ErrorType.ANDROID
+ ),
+ logger
+ )
+ errors.add(error)
+ return error
+ }
+
+ fun addThread(
+ id: String?,
+ name: String?,
+ errorType: ErrorType,
+ isErrorReportingThread: Boolean,
+ state: String
+ ): Thread {
+ val thread = Thread(
+ ThreadInternal(
+ id.toString(),
+ name.toString(),
+ errorType,
+ isErrorReportingThread,
+ state,
+ Stacktrace(ArrayList())
+ ),
+ logger
+ )
+ threads.add(thread)
+ return thread
+ }
+
+ fun leaveBreadcrumb(
+ message: String?,
+ type: BreadcrumbType?,
+ metadata: MutableMap?
+ ): Breadcrumb {
+ val breadcrumb = Breadcrumb(
+ message.toString(),
+ type ?: BreadcrumbType.MANUAL,
+ metadata,
+ Date(),
+ logger
+ )
+
+ breadcrumbs.add(breadcrumb)
+ return breadcrumb
+ }
}
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt
index d2da04b9ed..56e4794d8b 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt
@@ -7,7 +7,7 @@ import java.io.IOException
*/
class Notifier @JvmOverloads constructor(
var name: String = "Android Bugsnag Notifier",
- var version: String = "6.6.1",
+ var version: String = "6.7.0",
var url: String = "https://bugsnag.com"
) : JsonStream.Streamable {
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Stacktrace.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/Stacktrace.kt
index d7ea3f6b49..e846f7c6c9 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Stacktrace.kt
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Stacktrace.kt
@@ -1,6 +1,7 @@
package com.bugsnag.android
import java.io.IOException
+import kotlin.math.min
/**
* Serialize an exception stacktrace and mark frames as "in-project"
@@ -25,11 +26,35 @@ internal class Stacktrace : JsonStream.Streamable {
else -> null
}
}
+
+ fun serializeStackframe(
+ el: StackTraceElement,
+ projectPackages: Collection,
+ logger: Logger
+ ): Stackframe? {
+ try {
+ val className = el.className
+ val methodName = when {
+ className.isNotEmpty() -> className + "." + el.methodName
+ else -> el.methodName
+ }
+
+ return Stackframe(
+ methodName,
+ el.fileName ?: "Unknown",
+ el.lineNumber,
+ inProject(className, projectPackages)
+ )
+ } catch (lineEx: Exception) {
+ logger.w("Failed to serialize stacktrace", lineEx)
+ return null
+ }
+ }
}
- val trace: List
+ val trace: MutableList
- constructor(frames: List) {
+ constructor(frames: MutableList) {
trace = limitTraceLength(frames)
}
@@ -38,48 +63,26 @@ internal class Stacktrace : JsonStream.Streamable {
projectPackages: Collection,
logger: Logger
) {
- val frames = limitTraceLength(stacktrace)
- trace = frames.mapNotNull { serializeStackframe(it, projectPackages, logger) }
- }
-
- private fun limitTraceLength(frames: Array): Array {
- return when {
- frames.size >= STACKTRACE_TRIM_LENGTH -> frames.sliceArray(0 until STACKTRACE_TRIM_LENGTH)
- else -> frames
+ // avoid allocating new subLists or Arrays by only copying the required number of frames
+ // mapping them to our internal Stackframes as we go, roughly equivalent to
+ // stacktrace.take(STACKTRACE_TRIM_LENGTH).mapNotNullTo(ArrayList()) { ... }
+ val frameCount = min(STACKTRACE_TRIM_LENGTH, stacktrace.size)
+ trace = ArrayList(frameCount)
+ for (i in 0 until frameCount) {
+ val frame = serializeStackframe(stacktrace[i], projectPackages, logger)
+ if (frame != null) {
+ trace.add(frame)
+ }
}
}
- private fun limitTraceLength(frames: List): List {
+ private fun limitTraceLength(frames: MutableList): MutableList {
return when {
frames.size >= STACKTRACE_TRIM_LENGTH -> frames.subList(0, STACKTRACE_TRIM_LENGTH)
else -> frames
}
}
- private fun serializeStackframe(
- el: StackTraceElement,
- projectPackages: Collection,
- logger: Logger
- ): Stackframe? {
- try {
- val className = el.className
- val methodName = when {
- className.isNotEmpty() -> className + "." + el.methodName
- else -> el.methodName
- }
-
- return Stackframe(
- methodName,
- el.fileName ?: "Unknown",
- el.lineNumber,
- inProject(className, projectPackages)
- )
- } catch (lineEx: Exception) {
- logger.w("Failed to serialize stacktrace", lineEx)
- return null
- }
- }
-
@Throws(IOException::class)
override fun toStream(writer: JsonStream) {
writer.beginArray()
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Thread.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/Thread.java
index adf7855e69..a6368ab275 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Thread.java
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Thread.java
@@ -160,6 +160,16 @@ public List getStacktrace() {
return impl.getStacktrace();
}
+ /**
+ * Add a new stackframe to the end of this thread returning the new Stackframe data object.
+ */
+ @NonNull
+ public Stackframe addStackframe(@Nullable String method,
+ @Nullable String file,
+ long lineNumber) {
+ return impl.addStackframe(method, file, lineNumber);
+ }
+
@Override
public void toStream(@NonNull JsonStream stream) throws IOException {
impl.toStream(stream);
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/ThreadInternal.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/ThreadInternal.kt
index df29a8263f..0671ad6ec9 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/ThreadInternal.kt
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/ThreadInternal.kt
@@ -13,6 +13,12 @@ class ThreadInternal internal constructor(
var stacktrace: MutableList = stacktrace.trace.toMutableList()
+ fun addStackframe(method: String?, file: String?, lineNumber: Long): Stackframe {
+ val frame = Stackframe(method, file, lineNumber, null)
+ stacktrace.add(frame)
+ return frame
+ }
+
@Throws(IOException::class)
override fun toStream(writer: JsonStream) {
writer.beginObject()
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/ErrorFacadeTest.java b/bugsnag-android-core/src/test/java/com/bugsnag/android/ErrorFacadeTest.java
index dfe48a4d73..518907d8d1 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/ErrorFacadeTest.java
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/ErrorFacadeTest.java
@@ -3,10 +3,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import org.junit.Before;
import org.junit.Test;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -23,7 +25,7 @@ public class ErrorFacadeTest {
@Before
public void setUp() {
logger = new InterceptingLogger();
- trace = Collections.emptyList();
+ trace = new ArrayList<>();
ErrorInternal impl = new ErrorInternal("com.bar.CrashyClass",
"Whoops", new Stacktrace(trace), ErrorType.ANDROID);
error = new Error(impl, logger);
@@ -73,4 +75,30 @@ public void typeInvalid() {
public void stacktraceValid() {
assertEquals(trace, error.getStacktrace());
}
+
+ @Test
+ public void addStackframe() {
+ Stackframe frame = error.addStackframe(
+ "SomeClass.fakeMethod",
+ "NoSuchFile.dat",
+ 1234L
+ );
+
+ // check the new frame is the last frame in the error stacktrace
+ assertSame(frame, error.getStacktrace().get(error.getStacktrace().size() - 1));
+ assertEquals("SomeClass.fakeMethod", frame.getMethod());
+ assertEquals("NoSuchFile.dat", frame.getFile());
+ assertEquals(1234L, frame.getLineNumber());
+ }
+
+ @Test
+ public void addStackframeWithNulls() {
+ Stackframe frame = error.addStackframe(null, null, -1L);
+
+ // check the new frame is the last frame in the error stacktrace
+ assertSame(frame, error.getStacktrace().get(error.getStacktrace().size() - 1));
+ assertNull(frame.getMethod());
+ assertNull(frame.getFile());
+ assertEquals(-1L, frame.getLineNumber());
+ }
}
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/ErrorSerializationTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/ErrorSerializationTest.kt
index 4d81aba1c3..a74c7896c1 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/ErrorSerializationTest.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/ErrorSerializationTest.kt
@@ -14,13 +14,13 @@ internal class ErrorSerializationTest {
@Parameters
fun testCases() = generateSerializationTestCases(
"error",
- Error(ErrorInternal("foo", "bar", Stacktrace(listOf())), NoopLogger),
+ Error(ErrorInternal("foo", "bar", Stacktrace(mutableListOf())), NoopLogger),
Error(
ErrorInternal(
"foo",
"bar",
Stacktrace(
- listOf(
+ mutableListOf(
Stackframe(
method = "foo()",
file = "Bar.kt",
@@ -39,7 +39,7 @@ internal class ErrorSerializationTest {
"com.bugsnag.android.StacktraceSerializationTest",
"bar",
Stacktrace(
- listOf(
+ mutableListOf(
Stackframe(
method = "com.bugsnag.android.StacktraceSerializationTest\$Companion.inProject",
file = "StacktraceSerializationTest.kt",
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/EventFacadeTest.java b/bugsnag-android-core/src/test/java/com/bugsnag/android/EventFacadeTest.java
index cf8ee75f6c..c07113deb7 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/EventFacadeTest.java
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/EventFacadeTest.java
@@ -4,6 +4,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import com.bugsnag.android.internal.ImmutableConfig;
@@ -195,4 +196,66 @@ public void unhandledValid() {
event.setUnhandled(false);
assertFalse(event.isUnhandled());
}
+
+ @Test
+ public void addThread() {
+ Thread newThread = event.addThread(123L, "Magic Thread");
+ assertSame(newThread, event.getThreads().get(event.getThreads().size() - 1));
+ assertEquals("123", newThread.getId());
+ assertEquals("Magic Thread", newThread.getName());
+ assertEquals(ErrorType.ANDROID, newThread.getType());
+ assertEquals(Thread.State.RUNNABLE, newThread.getState());
+ assertEquals(0, newThread.getStacktrace().size());
+ }
+
+ @Test
+ public void addError() {
+ Error newError = event.addError("ErrorClass", "No error message");
+ assertSame(newError, event.getErrors().get(event.getErrors().size() - 1));
+ assertEquals("ErrorClass", newError.getErrorClass());
+ assertEquals("No error message", newError.getErrorMessage());
+ assertEquals(ErrorType.ANDROID, newError.getType());
+ assertEquals(0, newError.getStacktrace().size());
+ }
+
+ @Test
+ public void addErrorWithType() {
+ Error newError = event.addError("ErrorClass", "No error message", ErrorType.DART);
+ assertSame(newError, event.getErrors().get(event.getErrors().size() - 1));
+ assertEquals("ErrorClass", newError.getErrorClass());
+ assertEquals("No error message", newError.getErrorMessage());
+ assertEquals(ErrorType.DART, newError.getType());
+ assertEquals(0, newError.getStacktrace().size());
+ }
+
+ @Test
+ public void addErrorNullThrowable() {
+ Error newError = event.addError(null);
+ assertSame(newError, event.getErrors().get(event.getErrors().size() - 1));
+ assertEquals("null", newError.getErrorClass());
+ assertNull(newError.getErrorMessage());
+ assertEquals(ErrorType.ANDROID, newError.getType());
+ assertEquals(0, newError.getStacktrace().size());
+ }
+
+ @Test
+ public void addThreadWithNulls() {
+ Thread newThread = event.addThread(null, null);
+ assertSame(newThread, event.getThreads().get(event.getThreads().size() - 1));
+ assertEquals("null", newThread.getId());
+ assertEquals("null", newThread.getName());
+ assertEquals(ErrorType.ANDROID, newThread.getType());
+ assertEquals(Thread.State.RUNNABLE, newThread.getState());
+ assertEquals(0, newThread.getStacktrace().size());
+ }
+
+ @Test
+ public void addBadError() {
+ Error newError = event.addError(null, null);
+ assertSame(newError, event.getErrors().get(event.getErrors().size() - 1));
+ assertEquals("null", newError.getErrorClass());
+ assertNull(newError.getErrorMessage());
+ assertEquals(ErrorType.ANDROID, newError.getType());
+ assertEquals(0, newError.getStacktrace().size());
+ }
}
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/JsonUtils.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/JsonUtils.kt
index 2de6290f24..420bb8d5cd 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/JsonUtils.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/JsonUtils.kt
@@ -3,7 +3,6 @@ package com.bugsnag.android
import com.bugsnag.android.internal.JsonHelper
import org.junit.Assert
import java.io.StringWriter
-import java.lang.NullPointerException
/**
* Serializes a [JsonStream.Streamable] object into JSON and compares its equality against a JSON
@@ -86,15 +85,26 @@ internal fun verifyJsonParser(
}
/**
- * Generates parameterised test cases from a variable number of [JsonStream.Streamable] elements.
+ * Generates parameterised test cases from a variable number of elements.
* The expected JSON file for each element should match the naming format
* '$filename_serialization_$index.json'
*/
-internal fun generateSerializationTestCases(
+internal fun generateSerializationTestCases(filename: String, vararg elements: T) =
+ generateJsonTestCases(elements, "${filename}_serialization_")
+
+/**
+ * Generates parameterised test cases from a variable number of elements.
+ * The expected JSON file for each element should match the naming format
+ * '$filename_serialization_$index.json'
+ */
+internal fun generateDeserializationTestCases(filename: String, vararg elements: T) =
+ generateJsonTestCases(elements, "${filename}_deserialization_")
+
+private fun generateJsonTestCases(
+ elements: Array,
filename: String,
- vararg elements: T
): Collection> {
return elements.mapIndexed { index, obj ->
- Pair(obj, "${filename}_serialization_$index.json")
+ Pair(obj, "${filename}$index.json")
}
}
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/StacktraceSerializationTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/StacktraceSerializationTest.kt
index 7a8ef7416e..272de3e94e 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/StacktraceSerializationTest.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/StacktraceSerializationTest.kt
@@ -22,7 +22,7 @@ internal class StacktraceSerializationTest {
Stacktrace(arrayOf(), emptySet(), NoopLogger),
// empty custom frames ctor
- Stacktrace(listOf(frame)),
+ Stacktrace(mutableListOf(frame)),
// basic
basic(),
@@ -57,7 +57,7 @@ internal class StacktraceSerializationTest {
}
private fun trimStacktraceListCtor(): Stacktrace {
- val elements = (0..999).map { count ->
+ val elements = (0..999).mapTo(ArrayList()) { count ->
Stackframe("Foo", "Bar.kt", count, true).also { frame ->
// set different type for each frame
frame.type = when (count % 3) {
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/StacktraceTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/StacktraceTest.kt
index 53049a3d55..975a003b96 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/StacktraceTest.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/StacktraceTest.kt
@@ -7,7 +7,7 @@ class StacktraceTest {
@Test
fun stackframeListTrimmed() {
- val stackList = (1..300).map { index ->
+ val stackList = (1..300).mapTo(ArrayList()) { index ->
Stackframe("A", "B", index, true)
}
val stacktrace = Stacktrace(stackList)
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/ThreadDeserializationTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/ThreadDeserializationTest.kt
new file mode 100644
index 0000000000..12a5ffd627
--- /dev/null
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/ThreadDeserializationTest.kt
@@ -0,0 +1,68 @@
+package com.bugsnag.android
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+
+@RunWith(Parameterized::class)
+class ThreadDeserializationTest {
+ companion object {
+ @JvmStatic
+ @Parameters
+ fun testCases(): Collection> {
+ return generateDeserializationTestCases(
+ "thread",
+ Thread(
+ ThreadInternal(
+ "riker",
+ "will.riker",
+ ErrorType.C,
+ false,
+ "",
+ Stacktrace(mutableListOf())
+ ),
+ NoopLogger
+ ),
+ Thread(
+ ThreadInternal(
+ "321",
+ "mayne",
+ ErrorType.ANDROID,
+ false,
+ "",
+ Stacktrace(mutableListOf())
+ ),
+ NoopLogger
+ ),
+ Thread(
+ ThreadInternal(
+ "1415926535897932384626433832795028841971693993751058209749445923078" +
+ "164062862089986280348253421170679821480865132823066470938446095" +
+ "505822317253594081284811174502841027019385211055596446229489549" +
+ "303819644288109756659334461284756482337867831652712019091456485" +
+ "669234603486104543266482",
+ "smoke signal handler",
+ ErrorType.ANDROID,
+ false,
+ "happy",
+ Stacktrace(mutableListOf())
+ ),
+ NoopLogger
+ )
+ )
+ }
+ }
+
+ @Parameter
+ lateinit var testCase: Pair
+
+ private val eventMapper = BugsnagEventMapper(NoopLogger)
+
+ @Test
+ fun testJsonDeserialization() =
+ verifyJsonParser(testCase.first, testCase.second) {
+ eventMapper.convertThread(it)
+ }
+}
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/ThreadFacadeTest.java b/bugsnag-android-core/src/test/java/com/bugsnag/android/ThreadFacadeTest.java
index 18c49f490f..3d0e65ad96 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/ThreadFacadeTest.java
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/ThreadFacadeTest.java
@@ -3,6 +3,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
@@ -96,4 +98,30 @@ public void stacktraceInvalid() {
assertEquals(stacktrace.getTrace(), thread.getStacktrace());
assertNotNull(logger.getMsg());
}
+
+ @Test
+ public void addStackframe() {
+ Stackframe frame = thread.addStackframe(
+ "SomeClass.fakeMethod",
+ "NoSuchFile.dat",
+ 1234L
+ );
+
+ // check the new frame is the last frame in the thread stacktrace
+ assertSame(frame, thread.getStacktrace().get(thread.getStacktrace().size() - 1));
+ assertEquals("SomeClass.fakeMethod", frame.getMethod());
+ assertEquals("NoSuchFile.dat", frame.getFile());
+ assertEquals(1234L, frame.getLineNumber());
+ }
+
+ @Test
+ public void addStackframeWithNulls() {
+ Stackframe frame = thread.addStackframe(null, null, -1L);
+
+ // check the new frame is the last frame in the thread stacktrace
+ assertSame(frame, thread.getStacktrace().get(thread.getStacktrace().size() - 1));
+ assertNull(frame.getMethod());
+ assertNull(frame.getFile());
+ assertEquals(-1L, frame.getLineNumber());
+ }
}
diff --git a/bugsnag-android-core/src/test/resources/thread_deserialization_0.json b/bugsnag-android-core/src/test/resources/thread_deserialization_0.json
new file mode 100644
index 0000000000..6f4a9a5a6f
--- /dev/null
+++ b/bugsnag-android-core/src/test/resources/thread_deserialization_0.json
@@ -0,0 +1,5 @@
+{
+ "id": "riker",
+ "name": "will.riker",
+ "type": "c"
+}
\ No newline at end of file
diff --git a/bugsnag-android-core/src/test/resources/thread_deserialization_1.json b/bugsnag-android-core/src/test/resources/thread_deserialization_1.json
new file mode 100644
index 0000000000..85c029460c
--- /dev/null
+++ b/bugsnag-android-core/src/test/resources/thread_deserialization_1.json
@@ -0,0 +1,5 @@
+{
+ "id": 321,
+ "name": "mayne",
+ "type": "android"
+}
\ No newline at end of file
diff --git a/bugsnag-android-core/src/test/resources/thread_deserialization_2.json b/bugsnag-android-core/src/test/resources/thread_deserialization_2.json
new file mode 100644
index 0000000000..63a8d3342a
--- /dev/null
+++ b/bugsnag-android-core/src/test/resources/thread_deserialization_2.json
@@ -0,0 +1,6 @@
+{
+ "id": "1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482",
+ "name": "smoke signal handler",
+ "type": "android",
+ "state": "happy"
+}
\ No newline at end of file
diff --git a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c
index 13278dfceb..bce44c7dfd 100644
--- a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c
+++ b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c
@@ -27,10 +27,19 @@ static pthread_mutex_t bsg_global_env_write_mutex = PTHREAD_MUTEX_INITIALIZER;
/**
* All functions which will edit the environment (unless they are handling a
- * crash) must first request the lock
+ * crash) must first request the lock. Returns the bsg_environment that should
+ * be used, may return NULL if there is no valid bsg_environment (no lock
+ * will be held if this returns NULL)
*/
-static void request_env_write_lock(void) {
+static bsg_environment *request_env_write_lock(void) {
pthread_mutex_lock(&bsg_global_env_write_mutex);
+ bsg_environment *local_env = bsg_global_env;
+ if (local_env != NULL) {
+ return local_env;
+ } else {
+ pthread_mutex_unlock(&bsg_global_env_write_mutex);
+ return NULL;
+ }
}
/**
@@ -244,11 +253,11 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_install(
JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_addHandledEvent(JNIEnv *env,
jobject _this) {
- if (bsg_global_env == NULL) {
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
return;
}
- request_env_write_lock();
- bugsnag_event *event = &bsg_global_env->next_event;
+ bugsnag_event *event = &bsg_env->next_event;
if (bsg_event_has_session(event)) {
event->handled_events++;
@@ -259,11 +268,11 @@ Java_com_bugsnag_android_ndk_NativeBridge_addHandledEvent(JNIEnv *env,
JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_addUnhandledEvent(JNIEnv *env,
jobject _this) {
- if (bsg_global_env == NULL) {
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
return;
}
- request_env_write_lock();
- bugsnag_event *event = &bsg_global_env->next_event;
+ bugsnag_event *event = &bsg_env->next_event;
if (bsg_event_has_session(event)) {
event->unhandled_events++;
@@ -274,14 +283,17 @@ Java_com_bugsnag_android_ndk_NativeBridge_addUnhandledEvent(JNIEnv *env,
JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_startedSession(
JNIEnv *env, jobject _this, jstring session_id_, jstring start_date_,
jint handled_count, jint unhandled_count) {
- if (bsg_global_env == NULL || session_id_ == NULL) {
+ if (session_id_ == NULL) {
+ return;
+ }
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
return;
}
char *session_id = (char *)bsg_safe_get_string_utf_chars(env, session_id_);
char *started_at = (char *)bsg_safe_get_string_utf_chars(env, start_date_);
if (session_id != NULL && started_at != NULL) {
- request_env_write_lock();
- bsg_event_start_session(&bsg_global_env->next_event, session_id, started_at,
+ bsg_event_start_session(&bsg_env->next_event, session_id, started_at,
handled_count, unhandled_count);
release_env_write_lock();
}
@@ -291,11 +303,11 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_startedSession(
JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_pausedSession(
JNIEnv *env, jobject _this) {
- if (bsg_global_env == NULL) {
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
return;
}
- request_env_write_lock();
- bugsnag_event *event = &bsg_global_env->next_event;
+ bugsnag_event *event = &bsg_env->next_event;
memset(event->session_id, 0, bsg_strlen(event->session_id));
memset(event->session_start, 0, bsg_strlen(event->session_start));
event->handled_events = 0;
@@ -351,10 +363,14 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addBreadcrumb(
}
bsg_populate_crumb_metadata(env, crumb, metadata);
- request_env_write_lock();
- bsg_event_add_breadcrumb(&bsg_global_env->next_event, crumb);
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bsg_event_add_breadcrumb(&bsg_env->next_event, crumb);
release_env_write_lock();
+ end:
free(crumb);
}
bsg_safe_release_string_utf_chars(env, name_, name);
@@ -365,16 +381,17 @@ JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_updateAppVersion(JNIEnv *env,
jobject _this,
jstring new_value) {
- if (bsg_global_env == NULL) {
- return;
- }
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
}
- request_env_write_lock();
- bugsnag_app_set_version(&bsg_global_env->next_event, value);
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bugsnag_app_set_version(&bsg_env->next_event, value);
release_env_write_lock();
+end:
bsg_safe_release_string_utf_chars(env, new_value, value);
}
@@ -382,30 +399,31 @@ JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_updateBuildUUID(JNIEnv *env,
jobject _this,
jstring new_value) {
- if (bsg_global_env == NULL) {
- return;
- }
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
}
- request_env_write_lock();
- bugsnag_app_set_build_uuid(&bsg_global_env->next_event, value);
- release_env_write_lock();
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bugsnag_app_set_build_uuid(&bsg_env->next_event, value);
+end:
bsg_safe_release_string_utf_chars(env, new_value, value);
}
JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateContext(
JNIEnv *env, jobject _this, jstring new_value) {
- if (bsg_global_env == NULL) {
- return;
- }
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
}
- request_env_write_lock();
- bugsnag_event_set_context(&bsg_global_env->next_event, value);
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bugsnag_event_set_context(&bsg_env->next_event, value);
+end:
release_env_write_lock();
if (new_value != NULL) {
bsg_safe_release_string_utf_chars(env, new_value, value);
@@ -415,22 +433,22 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateContext(
JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_updateInForeground(
JNIEnv *env, jobject _this, jboolean new_value, jstring activity_) {
- if (bsg_global_env == NULL) {
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
return;
}
char *activity = (char *)bsg_safe_get_string_utf_chars(env, activity_);
- request_env_write_lock();
- bool was_in_foreground = bsg_global_env->next_event.app.in_foreground;
- bsg_global_env->next_event.app.in_foreground = (bool)new_value;
- bsg_strncpy(bsg_global_env->next_event.app.active_screen, activity,
- sizeof(bsg_global_env->next_event.app.active_screen));
+ bool was_in_foreground = bsg_env->next_event.app.in_foreground;
+ bsg_env->next_event.app.in_foreground = (bool)new_value;
+ bsg_strncpy(bsg_env->next_event.app.active_screen, activity,
+ sizeof(bsg_env->next_event.app.active_screen));
if ((bool)new_value) {
if (!was_in_foreground) {
- time(&bsg_global_env->foreground_start_time);
+ time(&bsg_env->foreground_start_time);
}
} else {
- bsg_global_env->foreground_start_time = 0;
- bsg_global_env->next_event.app.duration_in_foreground_ms_offset = 0;
+ bsg_env->foreground_start_time = 0;
+ bsg_env->next_event.app.duration_in_foreground_ms_offset = 0;
}
release_env_write_lock();
if (activity_ != NULL) {
@@ -441,12 +459,12 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateInForeground(
JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_updateIsLaunching(
JNIEnv *env, jobject _this, jboolean new_value) {
- if (bsg_global_env == NULL) {
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
return;
}
- request_env_write_lock();
- bugsnag_app_set_is_launching(&bsg_global_env->next_event, new_value);
- bsg_update_next_run_info(bsg_global_env);
+ bugsnag_app_set_is_launching(&bsg_env->next_event, new_value);
+ bsg_update_next_run_info(bsg_env);
release_env_write_lock();
}
@@ -454,9 +472,6 @@ JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_updateLowMemory(
JNIEnv *env, jobject _this, jboolean low_memory,
jstring memory_trim_level_description) {
- if (bsg_global_env == NULL) {
- return;
- }
char *memory_trim_level =
(char *)bsg_safe_get_string_utf_chars(env, memory_trim_level_description);
@@ -465,12 +480,16 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateLowMemory(
return;
}
- request_env_write_lock();
- bugsnag_event_add_metadata_bool(&bsg_global_env->next_event, "app",
- "lowMemory", (bool)low_memory);
- bugsnag_event_add_metadata_string(&bsg_global_env->next_event, "app",
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bugsnag_event_add_metadata_bool(&bsg_env->next_event, "app", "lowMemory",
+ (bool)low_memory);
+ bugsnag_event_add_metadata_string(&bsg_env->next_event, "app",
"memoryTrimLevel", memory_trim_level);
release_env_write_lock();
+end:
if (memory_trim_level_description != NULL) {
bsg_safe_release_string_utf_chars(env, memory_trim_level_description,
memory_trim_level);
@@ -481,17 +500,18 @@ JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_updateOrientation(JNIEnv *env,
jobject _this,
jstring new_value) {
- if (bsg_global_env == NULL) {
- return;
- }
-
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
}
- request_env_write_lock();
- bugsnag_device_set_orientation(&bsg_global_env->next_event, value);
+
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bugsnag_device_set_orientation(&bsg_env->next_event, value);
release_env_write_lock();
+end:
if (new_value != NULL) {
bsg_safe_release_string_utf_chars(env, new_value, value);
}
@@ -500,16 +520,17 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateOrientation(JNIEnv *env,
JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_updateReleaseStage(
JNIEnv *env, jobject _this, jstring new_value) {
- if (bsg_global_env == NULL) {
- return;
- }
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
}
- request_env_write_lock();
- bugsnag_app_set_release_stage(&bsg_global_env->next_event, value);
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bugsnag_app_set_release_stage(&bsg_env->next_event, value);
release_env_write_lock();
+end:
if (new_value != NULL) {
bsg_safe_release_string_utf_chars(env, new_value, value);
}
@@ -517,18 +538,20 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateReleaseStage(
JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateUserId(
JNIEnv *env, jobject _this, jstring new_value) {
- if (bsg_global_env == NULL) {
- return;
- }
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
}
- request_env_write_lock();
- bugsnag_event *event = &bsg_global_env->next_event;
+
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bugsnag_event *event = &bsg_env->next_event;
bugsnag_user user = bugsnag_event_get_user(event);
bugsnag_event_set_user(event, value, user.email, user.name);
release_env_write_lock();
+end:
if (new_value != NULL) {
bsg_safe_release_string_utf_chars(env, new_value, value);
}
@@ -536,18 +559,20 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateUserId(
JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateUserName(
JNIEnv *env, jobject _this, jstring new_value) {
- if (bsg_global_env == NULL) {
- return;
- }
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
}
- request_env_write_lock();
- bugsnag_event *event = &bsg_global_env->next_event;
+
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bugsnag_event *event = &bsg_env->next_event;
bugsnag_user user = bugsnag_event_get_user(event);
bugsnag_event_set_user(event, user.id, user.email, value);
release_env_write_lock();
+end:
if (new_value != NULL) {
bsg_safe_release_string_utf_chars(env, new_value, value);
}
@@ -557,18 +582,20 @@ JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_updateUserEmail(JNIEnv *env,
jobject _this,
jstring new_value) {
- if (bsg_global_env == NULL) {
- return;
- }
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
}
- request_env_write_lock();
- bugsnag_event *event = &bsg_global_env->next_event;
+
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bugsnag_event *event = &bsg_env->next_event;
bugsnag_user user = bugsnag_event_get_user(event);
bugsnag_event_set_user(event, user.id, value, user.name);
release_env_write_lock();
+end:
if (new_value != NULL) {
bsg_safe_release_string_utf_chars(env, new_value, value);
}
@@ -577,19 +604,19 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateUserEmail(JNIEnv *env,
JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_addMetadataString(
JNIEnv *env, jobject _this, jstring tab_, jstring key_, jstring value_) {
- if (bsg_global_env == NULL) {
- return;
- }
char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_);
char *key = (char *)bsg_safe_get_string_utf_chars(env, key_);
char *value = (char *)bsg_safe_get_string_utf_chars(env, value_);
if (tab != NULL && key != NULL && value != NULL) {
- request_env_write_lock();
- bugsnag_event_add_metadata_string(&bsg_global_env->next_event, tab, key,
- value);
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bugsnag_event_add_metadata_string(&bsg_env->next_event, tab, key, value);
release_env_write_lock();
}
+end:
bsg_safe_release_string_utf_chars(env, tab_, tab);
bsg_safe_release_string_utf_chars(env, key_, key);
bsg_safe_release_string_utf_chars(env, value_, value);
@@ -598,17 +625,18 @@ Java_com_bugsnag_android_ndk_NativeBridge_addMetadataString(
JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_addMetadataDouble(
JNIEnv *env, jobject _this, jstring tab_, jstring key_, jdouble value_) {
- if (bsg_global_env == NULL) {
- return;
- }
char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_);
char *key = (char *)bsg_safe_get_string_utf_chars(env, key_);
if (tab != NULL && key != NULL) {
- request_env_write_lock();
- bugsnag_event_add_metadata_double(&bsg_global_env->next_event, tab, key,
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bugsnag_event_add_metadata_double(&bsg_env->next_event, tab, key,
(double)value_);
}
release_env_write_lock();
+end:
bsg_safe_release_string_utf_chars(env, tab_, tab);
bsg_safe_release_string_utf_chars(env, key_, key);
}
@@ -616,17 +644,18 @@ Java_com_bugsnag_android_ndk_NativeBridge_addMetadataDouble(
JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_addMetadataBoolean(
JNIEnv *env, jobject _this, jstring tab_, jstring key_, jboolean value_) {
- if (bsg_global_env == NULL) {
- return;
- }
char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_);
char *key = (char *)bsg_safe_get_string_utf_chars(env, key_);
if (tab != NULL && key != NULL) {
- request_env_write_lock();
- bugsnag_event_add_metadata_bool(&bsg_global_env->next_event, tab, key,
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bugsnag_event_add_metadata_bool(&bsg_env->next_event, tab, key,
(bool)value_);
release_env_write_lock();
}
+end:
bsg_safe_release_string_utf_chars(env, tab_, tab);
bsg_safe_release_string_utf_chars(env, key_, key);
}
@@ -634,18 +663,19 @@ Java_com_bugsnag_android_ndk_NativeBridge_addMetadataBoolean(
JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_addMetadataOpaque(
JNIEnv *env, jobject _this, jstring tab_, jstring key_, jstring value_) {
- if (bsg_global_env == NULL) {
- return;
- }
char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_);
char *key = (char *)bsg_safe_get_string_utf_chars(env, key_);
char *value = (char *)bsg_safe_get_string_utf_chars(env, value_);
if (tab != NULL && key != NULL) {
- request_env_write_lock();
- bsg_add_metadata_value_opaque(&bsg_global_env->next_event.metadata, tab,
- key, value);
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bsg_add_metadata_value_opaque(&bsg_env->next_event.metadata, tab, key,
+ value);
release_env_write_lock();
}
+end:
bsg_safe_release_string_utf_chars(env, tab_, tab);
bsg_safe_release_string_utf_chars(env, key_, key);
bsg_safe_release_string_utf_chars(env, value_, value);
@@ -655,49 +685,51 @@ JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_clearMetadataTab(JNIEnv *env,
jobject _this,
jstring tab_) {
- if (bsg_global_env == NULL) {
- return;
- }
char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_);
if (tab == NULL) {
return;
}
- request_env_write_lock();
- bugsnag_event_clear_metadata_section(&bsg_global_env->next_event, tab);
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bugsnag_event_clear_metadata_section(&bsg_env->next_event, tab);
release_env_write_lock();
+end:
bsg_safe_release_string_utf_chars(env, tab_, tab);
}
JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_removeMetadata(
JNIEnv *env, jobject _this, jstring tab_, jstring key_) {
- if (bsg_global_env == NULL) {
- return;
- }
char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_);
char *key = (char *)bsg_safe_get_string_utf_chars(env, key_);
if (tab != NULL && key != NULL) {
- request_env_write_lock();
- bugsnag_event_clear_metadata(&bsg_global_env->next_event, tab, key);
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bugsnag_event_clear_metadata(&bsg_env->next_event, tab, key);
release_env_write_lock();
}
+end:
bsg_safe_release_string_utf_chars(env, tab_, tab);
bsg_safe_release_string_utf_chars(env, key_, key);
}
JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateMetadata(
JNIEnv *env, jobject _this, jobject metadata) {
- if (bsg_global_env == NULL) {
+ if (!bsg_jni_cache->initialized) {
+ BUGSNAG_LOG("updateMetadata failed: JNI cache not initialized.");
return;
}
- if (!bsg_jni_cache->initialized) {
- BUGSNAG_LOG("updateMetadata failed: JNI cache not initialized.");
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
return;
}
- request_env_write_lock();
- bsg_populate_metadata(env, &bsg_global_env->next_event.metadata, metadata);
+ bsg_populate_metadata(env, &bsg_env->next_event.metadata, metadata);
release_env_write_lock();
}
@@ -710,19 +742,19 @@ Java_com_bugsnag_android_ndk_NativeBridge_getSignalUnwindStackFunction(
JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addFeatureFlag(
JNIEnv *env, jobject thiz, jstring name_, jstring variant_) {
- if (bsg_global_env == NULL) {
- return;
- }
-
char *name = (char *)bsg_safe_get_string_utf_chars(env, name_);
char *variant = (char *)bsg_safe_get_string_utf_chars(env, variant_);
if (name != NULL) {
- request_env_write_lock();
- bsg_set_feature_flag(&bsg_global_env->next_event, name, variant);
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bsg_set_feature_flag(&bsg_env->next_event, name, variant);
release_env_write_lock();
}
+end:
bsg_safe_release_string_utf_chars(env, name_, name);
bsg_safe_release_string_utf_chars(env, variant_, variant);
}
@@ -732,30 +764,29 @@ Java_com_bugsnag_android_ndk_NativeBridge_clearFeatureFlag(JNIEnv *env,
jobject thiz,
jstring name_) {
- if (bsg_global_env == NULL) {
- return;
- }
-
char *name = (char *)bsg_safe_get_string_utf_chars(env, name_);
if (name != NULL) {
- request_env_write_lock();
- bsg_clear_feature_flag(&bsg_global_env->next_event, name);
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bsg_clear_feature_flag(&bsg_env->next_event, name);
release_env_write_lock();
}
+end:
bsg_safe_release_string_utf_chars(env, name_, name);
}
JNIEXPORT void JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_clearFeatureFlags(JNIEnv *env,
jobject thiz) {
- if (bsg_global_env == NULL) {
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
return;
}
-
- request_env_write_lock();
- bsg_free_feature_flags(&bsg_global_env->next_event);
+ bsg_free_feature_flags(&bsg_env->next_event);
release_env_write_lock();
}
@@ -769,10 +800,9 @@ JNIEXPORT jobject JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_getCurrentCallbackSetCounts(
JNIEnv *env, jobject thiz) {
- if (bsg_global_env == NULL) {
+ if (bsg_global_env == NULL || bsg_jni_cache == NULL) {
return NULL;
}
-
static const int total_callbacks =
sizeof(bsg_global_env->next_event.set_callback_counts) /
sizeof(*bsg_global_env->next_event.set_callback_counts);
@@ -803,7 +833,7 @@ Java_com_bugsnag_android_ndk_NativeBridge_getCurrentCallbackSetCounts(
JNIEXPORT jobject JNICALL
Java_com_bugsnag_android_ndk_NativeBridge_getCurrentNativeApiCallUsage(
JNIEnv *env, jobject thiz) {
- if (bsg_global_env == NULL) {
+ if (bsg_global_env == NULL || bsg_jni_cache == NULL) {
return NULL;
}
@@ -895,7 +925,13 @@ Java_com_bugsnag_android_ndk_NativeBridge_notifyAddCallback(JNIEnv *env,
return;
}
- bsg_notify_add_callback(&bsg_global_env->next_event, callback);
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bsg_notify_add_callback(&bsg_env->next_event, callback);
+ release_env_write_lock();
+end:
bsg_safe_release_string_utf_chars(env, callback_, callback);
}
@@ -907,7 +943,13 @@ Java_com_bugsnag_android_ndk_NativeBridge_notifyRemoveCallback(
return;
}
- bsg_notify_remove_callback(&bsg_global_env->next_event, callback);
+ bsg_environment *bsg_env = request_env_write_lock();
+ if (bsg_env == NULL) {
+ goto end;
+ }
+ bsg_notify_remove_callback(&bsg_env->next_event, callback);
+ release_env_write_lock();
+end:
bsg_safe_release_string_utf_chars(env, callback_, callback);
}
diff --git a/bugsnag-plugin-android-ndk/src/main/jni/internal_metrics.c b/bugsnag-plugin-android-ndk/src/main/jni/internal_metrics.c
index d30ab63100..ec7294b659 100644
--- a/bugsnag-plugin-android-ndk/src/main/jni/internal_metrics.c
+++ b/bugsnag-plugin-android-ndk/src/main/jni/internal_metrics.c
@@ -140,7 +140,8 @@ static void bsg_modify_callback_count(bugsnag_event *event, const char *api,
for (; i < total_callbacks && event->set_callback_counts[i].name[0] != 0;
i++) {
set_callback_count *callback_counter = &event->set_callback_counts[i];
- if (strcmp(callback_counter->name, api) == 0) {
+ if (strncmp(callback_counter->name, api, sizeof(callback_counter->name)) ==
+ 0) {
callback_counter->count += delta;
if (callback_counter->count < 0) {
callback_counter->count = 0;
@@ -157,13 +158,14 @@ static void bsg_modify_callback_count(bugsnag_event *event, const char *api,
void bsg_set_callback_count(bugsnag_event *event, const char *api,
int32_t count) {
- if (!internal_metrics_enabled || event == NULL) {
+ if (!internal_metrics_enabled || event == NULL || !api) {
return;
}
static const int total_callbacks =
sizeof(event->set_callback_counts) / sizeof(*event->set_callback_counts);
- if (strlen(api) >= sizeof(event->set_callback_counts[0].name)) {
+ if (strnlen(api, sizeof(event->set_callback_counts[0].name)) >=
+ sizeof(event->set_callback_counts[0].name)) {
// API name is too big to store.
return;
}
diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_writer.c b/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_writer.c
index 451982d800..935890205d 100644
--- a/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_writer.c
+++ b/bugsnag-plugin-android-ndk/src/main/jni/utils/serializer/event_writer.c
@@ -74,8 +74,8 @@ static size_t build_filename(bsg_environment *env, char *out) {
memcpy(out, env->event_path, length);
out[length++] = '/';
- // the timestamp is encoded as unix time
- length += bsg_uint64_to_string(now, &out[length]);
+ // the timestamp is encoded as unix time in millis
+ length += bsg_uint64_to_string(now * 1000uL, &out[length]);
// append the api_key to the filename
out[length++] = '_';
diff --git a/bugsnag-plugin-react-native/src/main/java/com/bugsnag/android/ErrorDeserializer.java b/bugsnag-plugin-react-native/src/main/java/com/bugsnag/android/ErrorDeserializer.java
index e81133688c..1e643d82d8 100644
--- a/bugsnag-plugin-react-native/src/main/java/com/bugsnag/android/ErrorDeserializer.java
+++ b/bugsnag-plugin-react-native/src/main/java/com/bugsnag/android/ErrorDeserializer.java
@@ -19,7 +19,7 @@ class ErrorDeserializer implements MapDeserializer {
public Error deserialize(Map map) {
String type = MapUtils.getOrThrow(map, "type");
List