Skip to content

Commit 200bec9

Browse files
adinauerclaude
andcommitted
fix(core): Avoid KeySetView in context serialization
Use ConcurrentHashMap.keys() when creating sorted context key snapshots so the serialization path stays compatible with Android API 21. Keep the array snapshot optimization without relying on KeySetView, which AnimalSniffer rejects for the SDK's minSdk. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3b13f34 commit 200bec9

4 files changed

Lines changed: 26 additions & 8 deletions

File tree

sentry/api/sentry.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7619,6 +7619,7 @@ public final class io/sentry/util/CollectionUtils {
76197619
public static fun newHashMap (Ljava/util/Map;)Ljava/util/Map;
76207620
public static fun reverseListIterator (Ljava/util/concurrent/CopyOnWriteArrayList;)Ljava/util/ListIterator;
76217621
public static fun size (Ljava/lang/Iterable;)I
7622+
public static fun toSortedStringArray (Ljava/util/Enumeration;I)[Ljava/lang/String;
76227623
}
76237624

76247625
public abstract interface class io/sentry/util/CollectionUtils$Mapper {

sentry/src/main/java/io/sentry/MonitorContexts.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
package io.sentry;
22

3+
import io.sentry.util.CollectionUtils;
34
import io.sentry.util.Objects;
45
import io.sentry.vendor.gson.stream.JsonToken;
56
import java.io.IOException;
6-
import java.util.Arrays;
77
import java.util.concurrent.ConcurrentHashMap;
88
import org.jetbrains.annotations.NotNull;
99
import org.jetbrains.annotations.Nullable;
1010

1111
public final class MonitorContexts extends ConcurrentHashMap<String, Object>
1212
implements JsonSerializable {
1313
private static final long serialVersionUID = 3987329379811822556L;
14-
private static final String[] EMPTY_KEYS = new String[0];
1514

1615
public MonitorContexts() {}
1716

@@ -49,8 +48,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger
4948
throws IOException {
5049
writer.beginObject();
5150
// Serialize in alphabetical order to keep determinism.
52-
final String[] sortedKeys = keySet().toArray(EMPTY_KEYS);
53-
Arrays.sort(sortedKeys);
51+
final String[] sortedKeys = CollectionUtils.toSortedStringArray(keys(), size());
5452
for (final String key : sortedKeys) {
5553
final Object value = get(key);
5654
if (value != null) {

sentry/src/main/java/io/sentry/protocol/Contexts.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
import io.sentry.ProfileContext;
1111
import io.sentry.SpanContext;
1212
import io.sentry.util.AutoClosableReentrantLock;
13+
import io.sentry.util.CollectionUtils;
1314
import io.sentry.util.HintUtils;
1415
import io.sentry.util.Objects;
1516
import io.sentry.vendor.gson.stream.JsonToken;
1617
import java.io.IOException;
17-
import java.util.Arrays;
1818
import java.util.Enumeration;
1919
import java.util.HashMap;
2020
import java.util.Map;
@@ -27,7 +27,6 @@
2727
public class Contexts implements JsonSerializable {
2828
private static final long serialVersionUID = 252445813254943011L;
2929
public static final String REPLAY_ID = "replay_id";
30-
private static final String[] EMPTY_KEYS = new String[0];
3130

3231
private final @NotNull ConcurrentHashMap<String, Object> internalStorage =
3332
new ConcurrentHashMap<>();
@@ -302,8 +301,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger
302301
throws IOException {
303302
writer.beginObject();
304303
// Serialize in alphabetical order to keep determinism.
305-
final String[] sortedKeys = internalStorage.keySet().toArray(EMPTY_KEYS);
306-
Arrays.sort(sortedKeys);
304+
final String[] sortedKeys = CollectionUtils.toSortedStringArray(keys(), internalStorage.size());
307305
for (final String key : sortedKeys) {
308306
final Object value = get(key);
309307
if (value != null) {

sentry/src/main/java/io/sentry/util/CollectionUtils.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package io.sentry.util;
22

33
import java.util.ArrayList;
4+
import java.util.Arrays;
45
import java.util.Collection;
6+
import java.util.Enumeration;
57
import java.util.HashMap;
68
import java.util.List;
79
import java.util.ListIterator;
@@ -15,9 +17,28 @@
1517
/** Util class for Collections */
1618
@ApiStatus.Internal
1719
public final class CollectionUtils {
20+
private static final String[] EMPTY_STRINGS = new String[0];
1821

1922
private CollectionUtils() {}
2023

24+
public static @NotNull String[] toSortedStringArray(
25+
final @NotNull Enumeration<String> source, final int size) {
26+
String[] sorted = size == 0 ? EMPTY_STRINGS : new String[size];
27+
int index = 0;
28+
while (source.hasMoreElements()) {
29+
if (index == sorted.length) {
30+
sorted = Arrays.copyOf(sorted, sorted.length + 1);
31+
}
32+
sorted[index] = source.nextElement();
33+
index++;
34+
}
35+
if (index != sorted.length) {
36+
sorted = Arrays.copyOf(sorted, index);
37+
}
38+
Arrays.sort(sorted);
39+
return sorted;
40+
}
41+
2142
/**
2243
* Returns an Iterator size
2344
*

0 commit comments

Comments
 (0)