Skip to content

Fix crash-time date calculation #2158

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

Merged
merged 3 commits into from
Feb 27, 2025
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
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
Loading