Skip to content

Commit f6edf41

Browse files
authored
Merge 98fb625 into 0675272
2 parents 0675272 + 98fb625 commit f6edf41

5 files changed

Lines changed: 439 additions & 19 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Fixes
6+
7+
- Fix `JsonObjectReader` and `MapObjectReader` hanging indefinitely when deserialization errors leave the reader in an inconsistent state; failed values are now skipped, parsing continues, and a WARNING-level log entry is emitted for each skipped value ([#5293](https://github.com/getsentry/sentry-java/pull/5293))
8+
59
### Dependencies
610

711
- Bump Native SDK from v0.13.3 to v0.13.6 ([#5277](https://github.com/getsentry/sentry-java/pull/5277))

sentry/src/main/java/io/sentry/JsonObjectReader.java

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
public final class JsonObjectReader implements ObjectReader {
1919

2020
private final @NotNull JsonReader jsonReader;
21+
private int depth = 0;
2122

2223
public JsonObjectReader(Reader in) {
2324
this.jsonReader = new JsonReader(in);
@@ -84,10 +85,18 @@ public float nextFloat() throws IOException {
8485

8586
@Override
8687
public void nextUnknown(ILogger logger, Map<String, Object> unknown, String name) {
88+
final int startDepth = depth;
89+
JsonToken startToken = JsonToken.END_DOCUMENT;
8790
try {
91+
startToken = peek();
8892
unknown.put(name, nextObjectOrNull());
8993
} catch (Exception exception) {
9094
logger.log(SentryLevel.ERROR, exception, "Error deserializing unknown key: %s", name);
95+
try {
96+
recoverValue(startDepth, startToken);
97+
} catch (Exception ignored) {
98+
// stream is unrecoverable
99+
}
91100
}
92101
}
93102

@@ -98,18 +107,28 @@ public void nextUnknown(ILogger logger, Map<String, Object> unknown, String name
98107
jsonReader.nextNull();
99108
return null;
100109
}
101-
jsonReader.beginArray();
110+
beginArray();
102111
List<T> list = new ArrayList<>();
103112
if (jsonReader.hasNext()) {
104113
do {
114+
final int startDepth = depth;
115+
final JsonToken startToken = peek();
105116
try {
106117
list.add(deserializer.deserialize(this, logger));
107118
} catch (Exception e) {
108-
logger.log(SentryLevel.WARNING, "Failed to deserialize object in list.", e);
119+
if (!recoverAfterValueFailure(
120+
logger,
121+
e,
122+
"Failed to deserialize object in list.",
123+
"Stream unrecoverable, aborting list deserialization.",
124+
startDepth,
125+
startToken)) {
126+
break;
127+
}
109128
}
110129
} while (jsonReader.peek() == JsonToken.BEGIN_OBJECT);
111130
}
112-
jsonReader.endArray();
131+
endArray();
113132
return list;
114133
}
115134

@@ -120,20 +139,30 @@ public void nextUnknown(ILogger logger, Map<String, Object> unknown, String name
120139
jsonReader.nextNull();
121140
return null;
122141
}
123-
jsonReader.beginObject();
142+
beginObject();
124143
Map<String, T> map = new HashMap<>();
125144
if (jsonReader.hasNext()) {
126145
do {
146+
final String key = jsonReader.nextName();
147+
final int startDepth = depth;
148+
final JsonToken startToken = peek();
127149
try {
128-
String key = jsonReader.nextName();
129150
map.put(key, deserializer.deserialize(this, logger));
130151
} catch (Exception e) {
131-
logger.log(SentryLevel.WARNING, "Failed to deserialize object in map.", e);
152+
if (!recoverAfterValueFailure(
153+
logger,
154+
e,
155+
"Failed to deserialize object in map.",
156+
"Stream unrecoverable, aborting map deserialization.",
157+
startDepth,
158+
startToken)) {
159+
break;
160+
}
132161
}
133162
} while (jsonReader.peek() == JsonToken.BEGIN_OBJECT || jsonReader.peek() == JsonToken.NAME);
134163
}
135164

136-
jsonReader.endObject();
165+
endObject();
137166
return map;
138167
}
139168

@@ -151,9 +180,23 @@ public void nextUnknown(ILogger logger, Map<String, Object> unknown, String name
151180
if (hasNext()) {
152181
do {
153182
final @NotNull String key = nextName();
154-
final @Nullable List<T> list = nextListOrNull(logger, deserializer);
155-
if (list != null) {
156-
result.put(key, list);
183+
final int startDepth = depth;
184+
final JsonToken startToken = peek();
185+
try {
186+
final @Nullable List<T> list = nextListOrNull(logger, deserializer);
187+
if (list != null) {
188+
result.put(key, list);
189+
}
190+
} catch (Exception e) {
191+
if (!recoverAfterValueFailure(
192+
logger,
193+
e,
194+
"Failed to deserialize list in map.",
195+
"Stream unrecoverable, aborting map-of-lists deserialization.",
196+
startDepth,
197+
startToken)) {
198+
break;
199+
}
157200
}
158201
} while (peek() == JsonToken.BEGIN_OBJECT || peek() == JsonToken.NAME);
159202
}
@@ -219,21 +262,25 @@ public void nextUnknown(ILogger logger, Map<String, Object> unknown, String name
219262
@Override
220263
public void beginObject() throws IOException {
221264
jsonReader.beginObject();
265+
depth++;
222266
}
223267

224268
@Override
225269
public void endObject() throws IOException {
226270
jsonReader.endObject();
271+
depth--;
227272
}
228273

229274
@Override
230275
public void beginArray() throws IOException {
231276
jsonReader.beginArray();
277+
depth++;
232278
}
233279

234280
@Override
235281
public void endArray() throws IOException {
236282
jsonReader.endArray();
283+
depth--;
237284
}
238285

239286
@Override
@@ -281,6 +328,42 @@ public void skipValue() throws IOException {
281328
jsonReader.skipValue();
282329
}
283330

331+
private boolean recoverAfterValueFailure(
332+
final @NotNull ILogger logger,
333+
final @NotNull Exception error,
334+
final @NotNull String warningMessage,
335+
final @NotNull String unrecoverableMessage,
336+
final int startDepth,
337+
final @NotNull JsonToken startToken) {
338+
logger.log(SentryLevel.WARNING, warningMessage, error);
339+
try {
340+
recoverValue(startDepth, startToken);
341+
return true;
342+
} catch (Exception recoveryException) {
343+
logger.log(SentryLevel.ERROR, unrecoverableMessage, recoveryException);
344+
return false;
345+
}
346+
}
347+
348+
private void recoverValue(final int startDepth, final @NotNull JsonToken startToken)
349+
throws IOException {
350+
final boolean enteredNestedValue = depth > startDepth;
351+
while (depth > startDepth) {
352+
final JsonToken token = peek();
353+
if (token == JsonToken.END_OBJECT) {
354+
endObject();
355+
} else if (token == JsonToken.END_ARRAY) {
356+
endArray();
357+
} else {
358+
skipValue();
359+
}
360+
}
361+
362+
if (!enteredNestedValue && peek() == startToken) {
363+
skipValue();
364+
}
365+
}
366+
284367
@Override
285368
public void close() throws IOException {
286369
jsonReader.close();

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

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ public MapObjectReader(final Map<String, Object> root) {
3131
@Override
3232
public void nextUnknown(
3333
final @NotNull ILogger logger, final Map<String, Object> unknown, final String name) {
34+
final int stackSizeBefore = stack.size();
3435
try {
3536
unknown.put(name, nextObjectOrNull());
3637
} catch (Exception exception) {
3738
logger.log(SentryLevel.ERROR, exception, "Error deserializing unknown key: %s", name);
39+
recoverValue(stackSizeBefore);
3840
}
3941
}
4042

@@ -52,10 +54,12 @@ public <T> List<T> nextListOrNull(
5254
List<T> list = new ArrayList<>();
5355
if (hasNext()) {
5456
do {
57+
final int stackSizeBefore = stack.size();
5558
try {
5659
list.add(deserializer.deserialize(this, logger));
5760
} catch (Exception e) {
5861
logger.log(SentryLevel.WARNING, "Failed to deserialize object in list.", e);
62+
recoverValue(stackSizeBefore);
5963
}
6064
} while (peek() == JsonToken.BEGIN_OBJECT);
6165
}
@@ -80,11 +84,13 @@ public <T> Map<String, T> nextMapOrNull(
8084
Map<String, T> map = new HashMap<>();
8185
if (hasNext()) {
8286
do {
87+
final String key = nextName();
88+
final int stackSizeBefore = stack.size();
8389
try {
84-
String key = nextName();
8590
map.put(key, deserializer.deserialize(this, logger));
8691
} catch (Exception e) {
8792
logger.log(SentryLevel.WARNING, "Failed to deserialize object in map.", e);
93+
recoverValue(stackSizeBefore);
8894
}
8995
} while (peek() == JsonToken.BEGIN_OBJECT || peek() == JsonToken.NAME);
9096
}
@@ -109,9 +115,15 @@ public <T> Map<String, T> nextMapOrNull(
109115
if (hasNext()) {
110116
do {
111117
final @NotNull String key = nextName();
112-
final @Nullable List<T> list = nextListOrNull(logger, deserializer);
113-
if (list != null) {
114-
result.put(key, list);
118+
final int stackSizeBefore = stack.size();
119+
try {
120+
final @Nullable List<T> list = nextListOrNull(logger, deserializer);
121+
if (list != null) {
122+
result.put(key, list);
123+
}
124+
} catch (Exception e) {
125+
logger.log(SentryLevel.WARNING, "Failed to deserialize list in map.", e);
126+
recoverValue(stackSizeBefore);
115127
}
116128
} while (peek() == JsonToken.BEGIN_OBJECT || peek() == JsonToken.NAME);
117129
}
@@ -197,12 +209,13 @@ public String nextName() throws IOException {
197209

198210
@Override
199211
public void beginObject() throws IOException {
200-
final Map.Entry<String, Object> currentEntry = stack.removeLast();
212+
final Map.Entry<String, Object> currentEntry = stack.peekLast();
201213
if (currentEntry == null) {
202214
throw new IOException("No more entries");
203215
}
204216
final Object value = currentEntry.getValue();
205217
if (value instanceof Map) {
218+
stack.removeLast();
206219
// insert a dummy entry to indicate end of an object
207220
stack.addLast(new AbstractMap.SimpleEntry<>(null, JsonToken.END_OBJECT));
208221
// extract map entries onto the stack
@@ -223,12 +236,13 @@ public void endObject() throws IOException {
223236

224237
@Override
225238
public void beginArray() throws IOException {
226-
final Map.Entry<String, Object> currentEntry = stack.removeLast();
239+
final Map.Entry<String, Object> currentEntry = stack.peekLast();
227240
if (currentEntry == null) {
228241
throw new IOException("No more entries");
229242
}
230243
final Object value = currentEntry.getValue();
231244
if (value instanceof List) {
245+
stack.removeLast();
232246
// insert a dummy entry to indicate end of an object
233247
stack.addLast(new AbstractMap.SimpleEntry<>(null, JsonToken.END_ARRAY));
234248
// extract map entries onto the stack
@@ -377,7 +391,17 @@ public void nextNull() throws IOException {
377391
public void setLenient(final boolean lenient) {}
378392

379393
@Override
380-
public void skipValue() throws IOException {}
394+
public void skipValue() throws IOException {
395+
if (!stack.isEmpty()) {
396+
stack.removeLast();
397+
}
398+
}
399+
400+
private void recoverValue(final int stackSizeBefore) {
401+
while (!stack.isEmpty() && stack.size() >= stackSizeBefore) {
402+
stack.removeLast();
403+
}
404+
}
381405

382406
@SuppressWarnings("TypeParameterUnusedInFormals")
383407
@Nullable

0 commit comments

Comments
 (0)