Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
- [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0138)
- [diff](https://github.com/getsentry/sentry-native/compare/0.13.7...0.13.8)

### Fixes

- Avoid stack overflow when deserializing large flat JSON objects([#5361](https://github.com/getsentry/sentry-java/pull/5361))

## 8.40.0

### Fixes
Expand Down
85 changes: 42 additions & 43 deletions sentry/src/main/java/io/sentry/JsonObjectDeserializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,49 +82,48 @@ private static final class TokenMap implements Token {

private void parse(@NotNull JsonObjectReader reader) throws IOException {
boolean done = false;
switch (reader.peek()) {
case BEGIN_ARRAY:
reader.beginArray();
pushCurrentToken(new TokenArray());
break;
case END_ARRAY:
reader.endArray();
done = handleArrayOrMapEnd();
break;
case BEGIN_OBJECT:
reader.beginObject();
pushCurrentToken(new TokenMap());
break;
case END_OBJECT:
reader.endObject();
done = handleArrayOrMapEnd();
break;
case NAME:
pushCurrentToken(new TokenName(reader.nextName()));
break;
case STRING:
// avoid method refs on Android due to some issues with older AGP setups
// noinspection Convert2MethodRef
done = handlePrimitive(() -> reader.nextString());
break;
case NUMBER:
done = handlePrimitive(() -> nextNumber(reader));
break;
case BOOLEAN:
// avoid method refs on Android due to some issues with older AGP setups
// noinspection Convert2MethodRef
done = handlePrimitive(() -> reader.nextBoolean());
break;
case NULL:
reader.nextNull();
done = handlePrimitive(() -> null);
break;
case END_DOCUMENT:
done = true;
break;
}
if (!done) {
parse(reader);
while (!done) {
switch (reader.peek()) {
case BEGIN_ARRAY:
reader.beginArray();
pushCurrentToken(new TokenArray());
break;
case END_ARRAY:
reader.endArray();
done = handleArrayOrMapEnd();
break;
case BEGIN_OBJECT:
reader.beginObject();
pushCurrentToken(new TokenMap());
break;
case END_OBJECT:
reader.endObject();
done = handleArrayOrMapEnd();
break;
case NAME:
pushCurrentToken(new TokenName(reader.nextName()));
break;
case STRING:
// avoid method refs on Android due to some issues with older AGP setups
// noinspection Convert2MethodRef
done = handlePrimitive(() -> reader.nextString());
break;
case NUMBER:
done = handlePrimitive(() -> nextNumber(reader));
break;
case BOOLEAN:
// avoid method refs on Android due to some issues with older AGP setups
// noinspection Convert2MethodRef
done = handlePrimitive(() -> reader.nextBoolean());
break;
case NULL:
reader.nextNull();
done = handlePrimitive(() -> null);
break;
case END_DOCUMENT:
done = true;
break;
}
}
}

Expand Down
44 changes: 44 additions & 0 deletions sentry/src/test/java/io/sentry/SentryEventTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package io.sentry
import io.sentry.exception.ExceptionMechanismException
import io.sentry.protocol.Mechanism
import io.sentry.protocol.SentryId
import java.io.StringReader
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.Collections
import java.util.concurrent.atomic.AtomicReference
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
Expand Down Expand Up @@ -174,6 +176,48 @@ class SentryEventTest {
}
}

@Test
fun `deserializes event with large flat modules map on a small stack`() {
val moduleCount = 50000
val json = buildString {
append("{\"event_id\":\"00000000000000000000000000000000\",\"modules\":{")
repeat(moduleCount) {
if (it > 0) {
append(',')
}
append("\"m")
append(it)
append("\":\"v\"")
}
append("}}")
}

val error = AtomicReference<Throwable?>()
val event = AtomicReference<SentryEvent?>()
val thread =
Thread(
null,
Runnable {
try {
event.set(
JsonSerializer(SentryOptions())
.deserialize(StringReader(json), SentryEvent::class.java)
)
} catch (throwable: Throwable) {
error.set(throwable)
}
},
"large-flat-modules-repro",
1024L * 1024L,
)

thread.start()
thread.join()

assertNull(error.get())
assertEquals(moduleCount, event.get()?.modules?.size)
}
Comment on lines +180 to +219
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this test fail with the previous (recursive) variant?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.


@Test
fun `null tag does not cause NPE`() {
val event = SentryEvent()
Expand Down
Loading