From cba95e09723ca1222805d35f8819b736c8e3450a Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 8 Jul 2026 11:19:56 +0200 Subject: [PATCH 1/7] fix(android): Avoid Math floor APIs Replace Math.floorDiv and Math.floorMod in the shared timestamp formatter with local helpers so Android consumers do not call APIs unavailable on older supported Android versions. Co-Authored-By: Claude --- .../io/sentry/vendor/SentryIso8601Utils.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/sentry/src/main/java/io/sentry/vendor/SentryIso8601Utils.java b/sentry/src/main/java/io/sentry/vendor/SentryIso8601Utils.java index b5cb1811aa..c3be18a2a7 100644 --- a/sentry/src/main/java/io/sentry/vendor/SentryIso8601Utils.java +++ b/sentry/src/main/java/io/sentry/vendor/SentryIso8601Utils.java @@ -142,8 +142,8 @@ public static long parseTimestamp(final @NotNull String timestamp) { return formatTimestampWithCalendar(millis); } - final long epochDay = Math.floorDiv(millis, MILLIS_PER_DAY); - int millisOfDay = (int) Math.floorMod(millis, MILLIS_PER_DAY); + final long epochDay = floorDiv(millis, MILLIS_PER_DAY); + int millisOfDay = (int) floorMod(millis, MILLIS_PER_DAY); final int[] yearMonthDay = epochDayToYearMonthDay(epochDay); final int hour = millisOfDay / (int) MILLIS_PER_HOUR; @@ -281,7 +281,7 @@ private static long epochMillis( private static long daysFromYearMonthDay(int year, final int month, final int day) { year -= month <= 2 ? 1 : 0; - final long era = Math.floorDiv(year, 400); + final long era = floorDiv(year, 400L); final int yearOfEra = (int) (year - era * 400); final int dayOfYear = (153 * (month + (month > 2 ? -3 : 9)) + 2) / 5 + day - 1; final int dayOfEra = yearOfEra * 365 + yearOfEra / 4 - yearOfEra / 100 + dayOfYear; @@ -290,7 +290,7 @@ private static long daysFromYearMonthDay(int year, final int month, final int da private static int[] epochDayToYearMonthDay(long epochDay) { epochDay += DAYS_0000_TO_1970; - final long era = Math.floorDiv(epochDay, 146097); + final long era = floorDiv(epochDay, 146097L); final int dayOfEra = (int) (epochDay - era * 146097); final int yearOfEra = (dayOfEra - dayOfEra / 1460 + dayOfEra / 36524 - dayOfEra / 146096) / 365; final int year = (int) (yearOfEra + era * 400); @@ -301,6 +301,18 @@ private static int[] epochDayToYearMonthDay(long epochDay) { return new int[] {year + (month <= 2 ? 1 : 0), month, day}; } + private static long floorDiv(final long x, final long y) { + long r = x / y; + if ((x ^ y) < 0 && r * y != x) { + r--; + } + return r; + } + + private static long floorMod(final long x, final long y) { + return x - floorDiv(x, y) * y; + } + private static boolean isBeforeGregorianCutover(final int year, final int month, final int day) { return year < 1582 || (year == 1582 && (month < 10 || (month == 10 && day < 15))); } From 074d7850742fcb8202892657e1c7f1f1a242bf49 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 8 Jul 2026 11:21:58 +0200 Subject: [PATCH 2/7] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24df41730a..0ac66b774f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ ### Fixes +- Avoid using `Math.floorDiv` and `Math.floorMod` on Android API levels where they are unavailable ([#5743](https://github.com/getsentry/sentry-java/pull/5743)) - Record byte-level client reports when event processors discard logs or trace metrics ([#5718](https://github.com/getsentry/sentry-java/pull/5718)) - Name the device-info caching thread `SentryDeviceInfoCache` so all threads spawned by the SDK are identifiable ([#5684](https://github.com/getsentry/sentry-java/pull/5684)) - Apply byte-category rate limits to log and trace metric envelope items ([#5716](https://github.com/getsentry/sentry-java/pull/5716)) From a8b63579fb118d0dfb76902cea7046f77300bdaf Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 8 Jul 2026 11:47:21 +0200 Subject: [PATCH 3/7] fix(android): Add attributed floor math helpers Move the floor division and modulo replacements into a dedicated SentryMath class with Android Open Source Project attribution and third-party notices. Add tests that compare the helper behavior against Java Math for edge cases. Co-Authored-By: Claude --- THIRD_PARTY_NOTICES.md | 28 +++++++++ .../io/sentry/vendor/SentryIso8601Utils.java | 20 ++----- .../java/io/sentry/vendor/SentryMath.java | 36 ++++++++++++ .../java/io/sentry/vendor/SentryMathTest.kt | 58 +++++++++++++++++++ 4 files changed, 126 insertions(+), 16 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/vendor/SentryMath.java create mode 100644 sentry/src/test/java/io/sentry/vendor/SentryMathTest.kt diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 7b87b92dcb..bad565e851 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -106,6 +106,34 @@ limitations under the License. --- +## Android Open Source Project — Math (Apache 2.0) + +**Source:** https://cs.android.com/android/platform/superproject/+/android-latest-release:libcore/ojluni/src/main/java/java/lang/Math.java;l=1587-1630;drc=eea9c17e2bf4cce9b17d601cdc3b44ccb559271b
+**License:** Apache License 2.0
+**Copyright:** Copyright (C) 2010 The Android Open Source Project + +### Scope + +The Sentry Java SDK includes adapted `floorDiv` and `floorMod` helpers from the Android Open Source Project's `Math` implementation to support older Android API levels. The code resides in `io.sentry.vendor.SentryMath`. + +``` +Copyright (C) 2010 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +--- + ## Square — Tape (Apache 2.0) **Source:** https://github.com/square/tape (Commit: 445cd3fd0a7b3ec48c9ea3e0e86663fe6d3735d8)
diff --git a/sentry/src/main/java/io/sentry/vendor/SentryIso8601Utils.java b/sentry/src/main/java/io/sentry/vendor/SentryIso8601Utils.java index c3be18a2a7..134a13e72a 100644 --- a/sentry/src/main/java/io/sentry/vendor/SentryIso8601Utils.java +++ b/sentry/src/main/java/io/sentry/vendor/SentryIso8601Utils.java @@ -142,8 +142,8 @@ public static long parseTimestamp(final @NotNull String timestamp) { return formatTimestampWithCalendar(millis); } - final long epochDay = floorDiv(millis, MILLIS_PER_DAY); - int millisOfDay = (int) floorMod(millis, MILLIS_PER_DAY); + final long epochDay = SentryMath.floorDiv(millis, MILLIS_PER_DAY); + int millisOfDay = (int) SentryMath.floorMod(millis, MILLIS_PER_DAY); final int[] yearMonthDay = epochDayToYearMonthDay(epochDay); final int hour = millisOfDay / (int) MILLIS_PER_HOUR; @@ -281,7 +281,7 @@ private static long epochMillis( private static long daysFromYearMonthDay(int year, final int month, final int day) { year -= month <= 2 ? 1 : 0; - final long era = floorDiv(year, 400L); + final long era = SentryMath.floorDiv(year, 400L); final int yearOfEra = (int) (year - era * 400); final int dayOfYear = (153 * (month + (month > 2 ? -3 : 9)) + 2) / 5 + day - 1; final int dayOfEra = yearOfEra * 365 + yearOfEra / 4 - yearOfEra / 100 + dayOfYear; @@ -290,7 +290,7 @@ private static long daysFromYearMonthDay(int year, final int month, final int da private static int[] epochDayToYearMonthDay(long epochDay) { epochDay += DAYS_0000_TO_1970; - final long era = floorDiv(epochDay, 146097L); + final long era = SentryMath.floorDiv(epochDay, 146097L); final int dayOfEra = (int) (epochDay - era * 146097); final int yearOfEra = (dayOfEra - dayOfEra / 1460 + dayOfEra / 36524 - dayOfEra / 146096) / 365; final int year = (int) (yearOfEra + era * 400); @@ -301,18 +301,6 @@ private static int[] epochDayToYearMonthDay(long epochDay) { return new int[] {year + (month <= 2 ? 1 : 0), month, day}; } - private static long floorDiv(final long x, final long y) { - long r = x / y; - if ((x ^ y) < 0 && r * y != x) { - r--; - } - return r; - } - - private static long floorMod(final long x, final long y) { - return x - floorDiv(x, y) * y; - } - private static boolean isBeforeGregorianCutover(final int year, final int month, final int day) { return year < 1582 || (year == 1582 && (month < 10 || (month == 10 && day < 15))); } diff --git a/sentry/src/main/java/io/sentry/vendor/SentryMath.java b/sentry/src/main/java/io/sentry/vendor/SentryMath.java new file mode 100644 index 0000000000..84a6b309d0 --- /dev/null +++ b/sentry/src/main/java/io/sentry/vendor/SentryMath.java @@ -0,0 +1,36 @@ +/* + * Adapted from https://cs.android.com/android/platform/superproject/+/android-latest-release:libcore/ojluni/src/main/java/java/lang/Math.java;l=1587-1630;drc=eea9c17e2bf4cce9b17d601cdc3b44ccb559271b + * + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sentry.vendor; + +final class SentryMath { + + private SentryMath() {} + + static long floorDiv(final long x, final long y) { + long r = x / y; + if ((x ^ y) < 0 && r * y != x) { + r--; + } + return r; + } + + static long floorMod(final long x, final long y) { + return x - floorDiv(x, y) * y; + } +} diff --git a/sentry/src/test/java/io/sentry/vendor/SentryMathTest.kt b/sentry/src/test/java/io/sentry/vendor/SentryMathTest.kt new file mode 100644 index 0000000000..32b9f8621d --- /dev/null +++ b/sentry/src/test/java/io/sentry/vendor/SentryMathTest.kt @@ -0,0 +1,58 @@ +package io.sentry.vendor + +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlin.test.assertFailsWith + +class SentryMathTest { + @Test + fun `floorDiv matches Java Math floorDiv`() { + values.forEach { x -> + divisors.forEach { y -> assertThat(SentryMath.floorDiv(x, y)).isEqualTo(Math.floorDiv(x, y)) } + } + } + + @Test + fun `floorMod matches Java Math floorMod`() { + values.forEach { x -> + divisors.forEach { y -> assertThat(SentryMath.floorMod(x, y)).isEqualTo(Math.floorMod(x, y)) } + } + } + + @Test + fun `floorDiv throws when dividing by zero`() { + assertFailsWith { SentryMath.floorDiv(1L, 0L) } + } + + @Test + fun `floorMod throws when dividing by zero`() { + assertFailsWith { SentryMath.floorMod(1L, 0L) } + } + + private companion object { + val values = + listOf( + Long.MIN_VALUE, + -86_400_001L, + -86_400_000L, + -86_399_999L, + -401L, + -400L, + -399L, + -2L, + -1L, + 0L, + 1L, + 2L, + 399L, + 400L, + 401L, + 86_399_999L, + 86_400_000L, + 86_400_001L, + Long.MAX_VALUE, + ) + + val divisors = listOf(Long.MIN_VALUE, -146_097L, -400L, -1L, 1L, 400L, 146_097L, Long.MAX_VALUE) + } +} From e9f735e74d9e578c9421310e0852460ea7e3a667 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 8 Jul 2026 11:57:24 +0200 Subject: [PATCH 4/7] fix(android): Use independent floor math helpers Remove the incorrect AOSP attribution for SentryMath. The referenced Android Math source is GPLv2 with the Classpath exception, not Apache 2.0. Implement the helpers directly from quotient and remainder semantics instead. Co-Authored-By: Claude --- THIRD_PARTY_NOTICES.md | 28 ---------------- .../java/io/sentry/vendor/SentryMath.java | 33 +++++-------------- 2 files changed, 9 insertions(+), 52 deletions(-) diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index bad565e851..7b87b92dcb 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -106,34 +106,6 @@ limitations under the License. --- -## Android Open Source Project — Math (Apache 2.0) - -**Source:** https://cs.android.com/android/platform/superproject/+/android-latest-release:libcore/ojluni/src/main/java/java/lang/Math.java;l=1587-1630;drc=eea9c17e2bf4cce9b17d601cdc3b44ccb559271b
-**License:** Apache License 2.0
-**Copyright:** Copyright (C) 2010 The Android Open Source Project - -### Scope - -The Sentry Java SDK includes adapted `floorDiv` and `floorMod` helpers from the Android Open Source Project's `Math` implementation to support older Android API levels. The code resides in `io.sentry.vendor.SentryMath`. - -``` -Copyright (C) 2010 The Android Open Source Project - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -``` - ---- - ## Square — Tape (Apache 2.0) **Source:** https://github.com/square/tape (Commit: 445cd3fd0a7b3ec48c9ea3e0e86663fe6d3735d8)
diff --git a/sentry/src/main/java/io/sentry/vendor/SentryMath.java b/sentry/src/main/java/io/sentry/vendor/SentryMath.java index 84a6b309d0..48d01f4182 100644 --- a/sentry/src/main/java/io/sentry/vendor/SentryMath.java +++ b/sentry/src/main/java/io/sentry/vendor/SentryMath.java @@ -1,21 +1,3 @@ -/* - * Adapted from https://cs.android.com/android/platform/superproject/+/android-latest-release:libcore/ojluni/src/main/java/java/lang/Math.java;l=1587-1630;drc=eea9c17e2bf4cce9b17d601cdc3b44ccb559271b - * - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package io.sentry.vendor; final class SentryMath { @@ -23,14 +5,17 @@ final class SentryMath { private SentryMath() {} static long floorDiv(final long x, final long y) { - long r = x / y; - if ((x ^ y) < 0 && r * y != x) { - r--; - } - return r; + final long quotient = x / y; + final long remainder = x % y; + return remainder != 0 && hasDifferentSigns(x, y) ? quotient - 1 : quotient; } static long floorMod(final long x, final long y) { - return x - floorDiv(x, y) * y; + final long remainder = x % y; + return remainder != 0 && hasDifferentSigns(x, y) ? remainder + y : remainder; + } + + private static boolean hasDifferentSigns(final long x, final long y) { + return x < 0 != y < 0; } } From fc9996a09340889cd58f10e63d2ca3ee107d79e7 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 8 Jul 2026 12:11:23 +0200 Subject: [PATCH 5/7] fix(android): Adapt floor math from Guava Use Apache-licensed Guava LongMath floor division logic for SentryMath and restore the matching third-party attribution. Keep floorMod implemented through floorDiv so it matches Math.floorMod for negative divisors too. Co-Authored-By: Claude --- THIRD_PARTY_NOTICES.md | 28 +++++++++++++++ .../java/io/sentry/vendor/SentryMath.java | 35 ++++++++++++++----- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 7b87b92dcb..381c39cfd6 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -34,6 +34,34 @@ limitations under the License. --- +## Google Guava — LongMath (Apache 2.0) + +**Source:** https://github.com/google/guava/blob/v33.0.0/guava/src/com/google/common/math/LongMath.java
+**License:** Apache License 2.0
+**Copyright:** Copyright (C) 2011 The Guava Authors + +### Scope + +The Sentry Java SDK includes adapted floor division logic from Guava's `LongMath` class to support older Android API levels. The code resides in `io.sentry.vendor.SentryMath`. + +``` +Copyright (C) 2011 The Guava Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +--- + ## FasterXML Jackson — ISO8601Utils (Apache 2.0) **Source:** https://github.com/FasterXML/jackson-databind
diff --git a/sentry/src/main/java/io/sentry/vendor/SentryMath.java b/sentry/src/main/java/io/sentry/vendor/SentryMath.java index 48d01f4182..50a8118d46 100644 --- a/sentry/src/main/java/io/sentry/vendor/SentryMath.java +++ b/sentry/src/main/java/io/sentry/vendor/SentryMath.java @@ -1,3 +1,21 @@ +/* + * Adapted from https://github.com/google/guava/blob/v33.0.0/guava/src/com/google/common/math/LongMath.java + * + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.sentry.vendor; final class SentryMath { @@ -6,16 +24,17 @@ private SentryMath() {} static long floorDiv(final long x, final long y) { final long quotient = x / y; - final long remainder = x % y; - return remainder != 0 && hasDifferentSigns(x, y) ? quotient - 1 : quotient; - } + final long remainder = x - y * quotient; - static long floorMod(final long x, final long y) { - final long remainder = x % y; - return remainder != 0 && hasDifferentSigns(x, y) ? remainder + y : remainder; + if (remainder == 0) { + return quotient; + } + + final int signum = 1 | (int) ((x ^ y) >> (Long.SIZE - 1)); + return signum < 0 ? quotient + signum : quotient; } - private static boolean hasDifferentSigns(final long x, final long y) { - return x < 0 != y < 0; + static long floorMod(final long x, final long y) { + return x - floorDiv(x, y) * y; } } From b5c02787972b24680ce3512ff7ce40f02bb3fe9f Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 8 Jul 2026 12:20:57 +0200 Subject: [PATCH 6/7] changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 188168ab5d..5c02469d1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ ### Fixes -- Avoid using `Math.floorDiv` and `Math.floorMod` on Android API levels where they are unavailable ([#5743](https://github.com/getsentry/sentry-java/pull/5743)) +- Fix ISO 8601 timestamp handling on older Android versions by replacing unsupported `Math.floorDiv` and `Math.floorMod` calls ([#5743](https://github.com/getsentry/sentry-java/pull/5743)) - Fix main thread identification parsing for ApplicationExitInfo ANRs ([#5733](https://github.com/getsentry/sentry-java/pull/5733)) - Do not send threads without stacktraces for ApplicationExitInfo ANRs ([#5733](https://github.com/getsentry/sentry-java/pull/5733)) - Record byte-level client reports when event processors discard logs or trace metrics ([#5718](https://github.com/getsentry/sentry-java/pull/5718)) From 59beb23f212f1491c507321da9d304c4fb0621d7 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 8 Jul 2026 12:59:02 +0200 Subject: [PATCH 7/7] docs(changelog): Mention Android NoSuchMethodError Update the unreleased changelog entry to call out the Android API compatibility failure more directly. Co-Authored-By: Claude --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c02469d1e..9bca5afb25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ ### Fixes -- Fix ISO 8601 timestamp handling on older Android versions by replacing unsupported `Math.floorDiv` and `Math.floorMod` calls ([#5743](https://github.com/getsentry/sentry-java/pull/5743)) +- Fix `NoSuchMethodError` when using `Math.floorDiv`/`Math.floorMod` on Android < 24 ([#5743](https://github.com/getsentry/sentry-java/pull/5743)) - Fix main thread identification parsing for ApplicationExitInfo ANRs ([#5733](https://github.com/getsentry/sentry-java/pull/5733)) - Do not send threads without stacktraces for ApplicationExitInfo ANRs ([#5733](https://github.com/getsentry/sentry-java/pull/5733)) - Record byte-level client reports when event processors discard logs or trace metrics ([#5718](https://github.com/getsentry/sentry-java/pull/5718))