Skip to content

Commit

Permalink
Merge pull request #2158 from bugsnag/PLAT-13769/off-by-one-error-dev…
Browse files Browse the repository at this point in the history
…ice-date

Fix crash-time date calculation
  • Loading branch information
lemnik authored Feb 27, 2025
2 parents 3a3c7ba + 7b95973 commit c6be745
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 7 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
* Excess threads (over `Configuration.getMaxReportedThreads`) are trimmed more reliably when the payload is modified before sending (in an `OnSendCallback` for example)
[#2148](https://github.com/bugsnag/bugsnag-android/pull/2148)

* Fixed an error calculating the device time during NDK crashes
[#2158](https://github.com/bugsnag/bugsnag-android/pull/2158)

## 6.12.0 (2025-02-18)

### Enhancements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ package com.bugsnag.android.ndk
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import java.io.File
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.GregorianCalendar
import java.util.Locale
import java.util.TimeZone

class NativeJsonSerializeTest {

Expand All @@ -16,6 +23,8 @@ class NativeJsonSerializeTest {
}

private val path = File(System.getProperty("java.io.tmpdir"), this::class.simpleName!!)
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
.apply { timeZone = TimeZone.getTimeZone("UTC") }

@Before
fun setupTmpdir() {
Expand All @@ -27,13 +36,84 @@ class NativeJsonSerializeTest {
path.deleteRecursively()
}

external fun run(outputDir: String): Int
external fun run(outputDir: String, timestamp: Long): Int

@Test
fun testPassesNativeSuite() {
verifyNativeRun(run(path.absolutePath))
fun testPassesNativeSuiteEpoch() {
verifyNativeRun(run(path.absolutePath, 7609))
val jsonFile = path.listFiles()!!.maxByOrNull { it.lastModified() }!!
val expected = loadJson("event_serialization.json")
assertEquals(expected, jsonFile.readText())
}

@Test
fun testRegression2024() {
val timestamp = GregorianCalendar(2024, 11, 30, 16, 0, 0).timeInMillis
val datestamp = dateFormat.format(Date(timestamp))

verifyNativeRun(run(path.absolutePath, timestamp / 1000L))
val jsonFile = path.listFiles()!!.maxByOrNull { it.lastModified() }!!
val expected = loadJson("event_serialization.json")
.replace("\"1970-01-01T02:06:49Z\"", "\"${datestamp}\"")
assertEquals(expected, jsonFile.readText())
}

@Test
fun testPassesNativeSuite2024() {
val timestamp = GregorianCalendar(2024, 0, 1).timeInMillis
val datestamp = dateFormat.format(Date(timestamp))

verifyNativeRun(run(path.absolutePath, timestamp / 1000L))
val jsonFile = path.listFiles()!!.maxByOrNull { it.lastModified() }!!
val expected = loadJson("event_serialization.json")
.replace("\"1970-01-01T02:06:49Z\"", "\"${datestamp}\"")
assertEquals(expected, jsonFile.readText())
}

@Test
fun testPassesNativeSuite2025() {
val timestamp = GregorianCalendar(2025, 1, 1).timeInMillis
val datestamp = dateFormat.format(Date(timestamp))

verifyNativeRun(run(path.absolutePath, timestamp / 1000L))
val jsonFile = path.listFiles()!!.maxByOrNull { it.lastModified() }!!
val expected = loadJson("event_serialization.json")
.replace("\"1970-01-01T02:06:49Z\"", "\"${datestamp}\"")
assertEquals(expected, jsonFile.readText())
}

@Test
fun testPassesNativeSuiteToday() {
val now = System.currentTimeMillis()
val datestamp = dateFormat.format(Date(now))

verifyNativeRun(run(path.absolutePath, now / 1000L))
val jsonFile = path.listFiles()!!.maxByOrNull { it.lastModified() }!!
val expected = loadJson("event_serialization.json")
.replace("\"1970-01-01T02:06:49Z\"", "\"${datestamp}\"")
assertEquals(expected, jsonFile.readText())
}

@Test
@Ignore("useful when working on the date formatting code")
fun testDecadesOfDates() {
val calendar = Calendar.getInstance().apply { add(Calendar.YEAR, -10) }
val end = Calendar.getInstance().apply { add(Calendar.YEAR, 10) }

while (calendar < end) {
val instant = calendar.timeInMillis
val datestamp = dateFormat.format(Date(instant))

verifyNativeRun(run(path.absolutePath, instant / 1000L))
val jsonFile = path.listFiles()!!.maxByOrNull { it.lastModified() }!!
val expected = loadJson("event_serialization.json")
.replace("\"1970-01-01T02:06:49Z\"", "\"${datestamp}\"")
assertEquals(expected, jsonFile.readText())

// move the date along 6 hours at a time
calendar.add(Calendar.HOUR, 6)

jsonFile.delete()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

#include "BSG_KSCrashStringConversion.h"
#include "utils/logger.h"
#include <math.h>
#include <memory.h>
#include <time.h>
Expand Down Expand Up @@ -218,9 +219,19 @@ static void safe_gmtime_r(time_t time, struct tm *out) {
days -= quotient * days_per_4years;
years += quotient * 4;

quotient = days / 365;
days -= quotient * 365;
years += quotient;
while (days >= 365) {
if (years % 4 == 0 && (years % 100 != 0 || years % 400 == 0)) {
if (days >= 366) {
days -= 366;
years += 1;
} else {
break;
}
} else {
days -= 365;
years += 1;
}
}

out->tm_year = years - 1900;
out->tm_yday = days;
Expand Down
5 changes: 4 additions & 1 deletion bugsnag-plugin-android-ndk/src/test/cpp/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Java_com_bugsnag_android_ndk_NativeStringTest_run(JNIEnv *_env, jobject _this) {
extern bool bsg_event_write(bsg_environment *env);

JNIEXPORT int JNICALL Java_com_bugsnag_android_ndk_NativeJsonSerializeTest_run(
JNIEnv *_env, jobject _this, jstring _dir) {
JNIEnv *_env, jobject _this, jstring _dir, jlong timestamp) {

const char *dir = (*_env)->GetStringUTFChars(_env, _dir, NULL);
if (dir == NULL) {
Expand All @@ -71,12 +71,15 @@ JNIEXPORT int JNICALL Java_com_bugsnag_android_ndk_NativeJsonSerializeTest_run(
bugsnag_event *event = init_event();
memcpy(&env.next_event, event, sizeof(bugsnag_event));

env.next_event.device.time = (time_t) timestamp;
env.event_path = strdup(dir);
env.static_json_data = NULL;
strcpy(env.event_uuid, "test-uuid");

bsg_event_write(&env);

free(event);
free(env.event_path);

(*_env)->ReleaseStringUTFChars(_env, _dir, dir);

Expand Down
4 changes: 4 additions & 0 deletions features/support/env.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@
skip_this_scenario("Skipping scenario") if Maze.config.os_version < 5
end

Before('@skip_android_14') do |scenario|
skip_this_scenario("Skipping scenario") if Maze.config.os_version.floor == 14
end

Before('@skip_android_13') do |scenario|
skip_this_scenario("Skipping scenario") if Maze.config.os_version.floor == 13
end
Expand Down

0 comments on commit c6be745

Please sign in to comment.