From 5f171dbb2fe38bde7119c0274ccc94af8b1b9ee8 Mon Sep 17 00:00:00 2001 From: hocaron Date: Mon, 12 Oct 2026 23:34:36 +0900 Subject: [PATCH 1/2] fix(scopes): Walk parent chain iteratively in isAncestorOf isAncestorOf recursed once per parent link, so a long Scopes chain (e.g. from a task that keeps re-scheduling itself through SentryScheduleHook) could throw StackOverflowError. Since #6004 this is also called from Sentry.getCurrentScopes() in globalHubMode. Walk the chain with a loop instead. Results are unchanged. --- CHANGELOG.md | 6 ++++ sentry/src/main/java/io/sentry/Scopes.java | 17 ++++------- sentry/src/test/java/io/sentry/ScopesTest.kt | 32 ++++++++++++++++++++ 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbf192eeb33..3df31b32270 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Avoid `StackOverflowError` in `Scopes.isAncestorOf` on deep `Scopes` chains ([#NNNN](https://github.com/getsentry/sentry-java/pull/NNNN)) + ## 8.60.0 ### Features diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index d3926d56993..7eccbabd8b5 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -96,18 +96,13 @@ private Scopes( @Override @ApiStatus.Internal public boolean isAncestorOf(final @Nullable IScopes otherScopes) { - if (otherScopes == null) { - return false; - } - - if (this == otherScopes) { - return true; - } - - if (otherScopes.getParentScopes() != null) { - return isAncestorOf(otherScopes.getParentScopes()); + @Nullable IScopes candidate = otherScopes; + while (candidate != null) { + if (this == candidate) { + return true; + } + candidate = candidate.getParentScopes(); } - return false; } diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index 13a57ebe70a..7e4a71d5804 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -169,6 +169,38 @@ class ScopesTest { assertEquals("b", cloneScope!!.tags["scopes"]) } + @Test + fun `isAncestorOf does not overflow the stack on a deep chain`() { + val options = SentryOptions() + options.cacheDirPath = file.absolutePath + options.dsn = "https://key@sentry.io/proj" + options.setSerializer(mock()) + val root = createScopes(options) + var deepest: IScopes = root + repeat(20_000) { deepest = deepest.forkedCurrentScope("deep") } + + val result = AtomicReference() + val thread = + Thread( + null, + { + result.set( + try { + root.isAncestorOf(deepest) + } catch (e: StackOverflowError) { + e + } + ) + }, + "small-stack", + 256 * 1024, + ) + thread.start() + thread.join() + + assertEquals(true, result.get()) + } + @Test fun `when scopes is initialized, breadcrumbs are capped as per options`() { val options = SentryOptions() From d34f9ac3ebac3b919b3b550436678720d84f654e Mon Sep 17 00:00:00 2001 From: hocaron Date: Tue, 13 Oct 2026 01:15:20 +0900 Subject: [PATCH 2/2] Add PR number to changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3df31b32270..cf8fe55f6bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixes -- Avoid `StackOverflowError` in `Scopes.isAncestorOf` on deep `Scopes` chains ([#NNNN](https://github.com/getsentry/sentry-java/pull/NNNN)) +- Avoid `StackOverflowError` in `Scopes.isAncestorOf` on deep `Scopes` chains ([#6249](https://github.com/getsentry/sentry-java/pull/6249)) ## 8.60.0