Skip to content

Detect cyclic references during JSONObject/JSONArray serialization - #1075

Open
arimu1 wants to merge 2 commits into
stleary:masterfrom
arimu1:fix-1056-cyclic-serialize
Open

Detect cyclic references during JSONObject/JSONArray serialization#1075
arimu1 wants to merge 2 commits into
stleary:masterfrom
arimu1:fix-1056-cyclic-serialize

Conversation

@arimu1

@arimu1 arimu1 commented Sep 2, 2026

Copy link
Copy Markdown

Summary

  • Thread an identity-based serialization stack (IdentityHashMap-backed Set) through JSONObject.write, JSONArray.write, and writeValue so programmatic self-references and mutual cycles throw JSONException instead of StackOverflowError.
  • Use add/remove-in-finally so diamond graphs (shared child referenced from multiple keys) still serialize correctly.
  • Re-throw cyclic-reference JSONException from toString() while preserving the existing null-return behavior for other serialization failures.

Fixes #1056

Notes on depth limiting

This PR focuses on cycle detection for programmatically constructed JSONObject/JSONArray graphs (the reproducer in #1056). Deep-but-acyclic parsed JSON that can still overflow the stack during serialization (see @JeewoongKim's fuzz note) is left as a follow-up: applying maxNestingDepth on the write path would need a separate design decision about whether serialization should share parser configuration.

Test plan

  • mvn test (800 tests, 0 failures)
  • Direct self-referential JSONObject.toString()JSONException
  • Mutual A↔B cycle → JSONException
  • Self-referential JSONArray.toString()JSONException
  • Diamond/shared-child graph still serializes
  • JSONObject.valueToString() on cyclic graph → JSONException
  • Mixed JSONObjectJSONArray cycle → typed CyclicReferenceException
  • BrokenToString values still yield null from no-arg toString()

Thread an identity-based serialization stack through write paths so
programmatic cycles throw JSONException instead of StackOverflowError.
Re-throw cyclic-reference JSONException from toString() while preserving
existing null-return behavior for other serialization failures.

Fixes stleary#1056
… contract

Add package-private CyclicReferenceException and detect cycles via instanceof
instead of message matching. Re-throw only cyclic failures from no-arg
toString(); other JSONException paths still return null. Strengthen tests
with typed assertions, BrokenToString null checks, and mixed object/array cycle.

Fixes review feedback on stleary#1075
@sonarqubecloud

sonarqubecloud Bot commented Sep 2, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSONObject.toString() throws StackOverflowError (not JSONException) on self-referential cycles

1 participant