Skip to content

Commit 35aae51

Browse files
antonisclaude
andauthored
fix(android): Fix ConcurrentModificationException (#5588)
* fix(android): Fix ConcurrentModificationException when disabling native crash handling When enableNativeCrashHandling is set to false, the code was iterating over the integrations list with a for-each loop while calling remove() directly, which causes a ConcurrentModificationException at runtime. Fixed by using Java 8's removeIf() method which safely handles iteration and removal in a single operation. This is more concise and follows modern Java best practices. Added unit tests to verify the fix and ensure integrations are properly removed without throwing exceptions. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Lint fix --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 44282b5 commit 35aae51

File tree

2 files changed

+44
-10
lines changed

2 files changed

+44
-10
lines changed

packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryStartTest.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,41 @@ class RNSentryStartTest {
236236
assertEquals("android", result?.getTag("event.origin"))
237237
assertEquals("java", result?.getTag("event.environment"))
238238
}
239+
240+
@Test
241+
fun `when enableNativeCrashHandling is false, native crash integrations are removed without ConcurrentModificationException`() {
242+
val rnOptions = JavaOnlyMap.of("enableNativeCrashHandling", false)
243+
val options = SentryAndroidOptions()
244+
245+
// This should not throw ConcurrentModificationException
246+
RNSentryStart.getSentryAndroidOptions(options, rnOptions, logger)
247+
248+
// Verify integrations were removed
249+
val integrations = options.getIntegrations()
250+
assertFalse(
251+
"UncaughtExceptionHandlerIntegration should be removed",
252+
integrations.any { it is io.sentry.UncaughtExceptionHandlerIntegration },
253+
)
254+
assertFalse(
255+
"AnrIntegration should be removed",
256+
integrations.any { it is io.sentry.android.core.AnrIntegration },
257+
)
258+
assertFalse(
259+
"NdkIntegration should be removed",
260+
integrations.any { it is io.sentry.android.core.NdkIntegration },
261+
)
262+
}
263+
264+
@Test
265+
fun `when enableNativeCrashHandling is true, native crash integrations are kept`() {
266+
val rnOptions = JavaOnlyMap.of("enableNativeCrashHandling", true)
267+
val options = SentryAndroidOptions()
268+
269+
RNSentryStart.getSentryAndroidOptions(options, rnOptions, logger)
270+
271+
// When enabled, the default integrations should still be present
272+
// Note: This test verifies that we don't remove integrations when the flag is true
273+
val integrations = options.getIntegrations()
274+
assertNotNull("Integrations list should not be null", integrations)
275+
}
239276
}

packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import com.facebook.react.bridge.ReadableType;
77
import com.facebook.react.common.JavascriptException;
88
import io.sentry.ILogger;
9-
import io.sentry.Integration;
109
import io.sentry.ProfileLifecycle;
1110
import io.sentry.Sentry;
1211
import io.sentry.SentryEvent;
@@ -25,7 +24,6 @@
2524
import io.sentry.react.replay.RNSentryReplayUnmask;
2625
import java.net.URI;
2726
import java.net.URISyntaxException;
28-
import java.util.List;
2927
import org.jetbrains.annotations.NotNull;
3028
import org.jetbrains.annotations.Nullable;
3129

@@ -183,14 +181,13 @@ static void getSentryAndroidOptions(
183181

184182
if (rnOptions.hasKey("enableNativeCrashHandling")
185183
&& !rnOptions.getBoolean("enableNativeCrashHandling")) {
186-
final List<Integration> integrations = options.getIntegrations();
187-
for (final Integration integration : integrations) {
188-
if (integration instanceof UncaughtExceptionHandlerIntegration
189-
|| integration instanceof AnrIntegration
190-
|| integration instanceof NdkIntegration) {
191-
integrations.remove(integration);
192-
}
193-
}
184+
options
185+
.getIntegrations()
186+
.removeIf(
187+
integration ->
188+
integration instanceof UncaughtExceptionHandlerIntegration
189+
|| integration instanceof AnrIntegration
190+
|| integration instanceof NdkIntegration);
194191
}
195192
logger.log(
196193
SentryLevel.INFO, String.format("Native Integrations '%s'", options.getIntegrations()));

0 commit comments

Comments
 (0)