Skip to content

Commit 0f38c5f

Browse files
adinauerclaude
andcommitted
fix(sentry): Continue list recovery until array end
Continue JsonObjectReader list deserialization until the array boundary instead of stopping when the next token is not BEGIN_OBJECT. After recovering from a failed object element, the next unread token can be a primitive. The previous loop exited early in that case and then aborted when endArray() encountered unread content. Iterating with hasNext() keeps recovery aligned with the actual array boundary. Add a regression test covering a failing object followed by a primitive and a later valid object. Fixes GH-5278 Co-Authored-By: Claude <noreply@anthropic.com>
1 parent c9d3909 commit 0f38c5f

2 files changed

Lines changed: 40 additions & 17 deletions

File tree

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

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -120,24 +120,22 @@ public void nextUnknown(ILogger logger, Map<String, Object> unknown, String name
120120
}
121121
beginArray();
122122
List<T> list = new ArrayList<>();
123-
if (jsonReader.hasNext()) {
124-
do {
125-
final RecoveryState recoveryState = beginRecovery(peek());
126-
try {
127-
list.add(deserializer.deserialize(this, logger));
128-
} catch (Exception e) {
129-
if (!recoverAfterValueFailure(
130-
logger,
131-
e,
132-
"Failed to deserialize object in list.",
133-
"Stream unrecoverable, aborting list deserialization.",
134-
recoveryState)) {
135-
break;
136-
}
137-
} finally {
138-
endRecovery(recoveryState);
123+
while (jsonReader.hasNext()) {
124+
final RecoveryState recoveryState = beginRecovery(peek());
125+
try {
126+
list.add(deserializer.deserialize(this, logger));
127+
} catch (Exception e) {
128+
if (!recoverAfterValueFailure(
129+
logger,
130+
e,
131+
"Failed to deserialize object in list.",
132+
"Stream unrecoverable, aborting list deserialization.",
133+
recoveryState)) {
134+
break;
139135
}
140-
} while (jsonReader.peek() == JsonToken.BEGIN_OBJECT);
136+
} finally {
137+
endRecovery(recoveryState);
138+
}
141139
}
142140
endArray();
143141
return list;

sentry/src/test/java/io/sentry/JsonObjectReaderTest.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,31 @@ class JsonObjectReaderTest {
298298
assertEquals(listOf("two"), actual)
299299
}
300300

301+
@Test
302+
fun `nextListOrNull keeps elements after a failing object followed by a primitive`() {
303+
val reader =
304+
getValuesReader(
305+
"[{\"kind\": \"fail\", \"value\": \"bad\"}, \"oops\", {\"kind\": \"ok\", \"value\": \"two\"}]"
306+
)
307+
val deserializer =
308+
JsonDeserializer<String> { objectReader, _ ->
309+
objectReader.beginObject()
310+
objectReader.nextName()
311+
if (objectReader.nextString() == "fail") {
312+
throw IllegalStateException("intentional")
313+
}
314+
objectReader.nextName()
315+
val value = objectReader.nextString()
316+
objectReader.endObject()
317+
value
318+
}
319+
320+
val actual = reader.nextListOrNull(fixture.logger, deserializer)
321+
322+
assertEquals(listOf("two"), actual)
323+
assertEquals(io.sentry.vendor.gson.stream.JsonToken.END_OBJECT, reader.peek())
324+
}
325+
301326
@Test
302327
fun `nextListOrNull keeps elements after skipValue consumes a failing element`() {
303328
var callCount = 0

0 commit comments

Comments
 (0)