Fix DOM parse() lifetime on the Vector backend (B17)#30
Merged
Conversation
The Vector backend returned lazy tape-backed views over parser-owned buffers, so reusing the parser silently corrupted previously returned JsonObject/JsonArray. Materialize the DOM eagerly (as JNI/Native already do), drop the dual tape/list representation from JsonObject/JsonArray, move Tape out of commonMain, document the materialization guarantee in parse() KDoc, and lock the contract with cross-backend regression tests.
There was a problem hiding this comment.
Pull request overview
This PR fixes a correctness/lifetime issue in the Vector (pure Kotlin) backend where parse() previously returned lazy DOM views over a parser-reused tape + string buffer, causing previously returned JsonObject/JsonArray values to become silently corrupted after subsequent parse()/iterate() calls. The change aligns Vector DOM behavior with JNI/Native by eagerly materializing an independent object graph and updates tests/KDoc to enforce the intended immutability and parser-reuse contract.
Changes:
- Eagerly materialize Vector DOM results into list-backed
JsonObject/JsonArraystructures so results survive parser reuse,iterate(), andclose(). - Remove the tape-view representation from
JsonObject/JsonArray(DOM now consistently list-backed across backends). - Add cross-backend regression tests covering parser reuse, DOM survival across
iterate(), and DOM survival afterclose(); updateparse()KDoc to state the guarantee.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| simdjson-kotlin/src/jvmMain/kotlin/io/github/devcrocod/simdjson/TapeValueMaterializer.kt | New JVM Vector materializer that recursively builds list-backed DOM nodes from the tape + string buffer. |
| simdjson-kotlin/src/jvmAndAndroidMain/kotlin/io/github/devcrocod/simdjson/Tape.kt | Moves Tape to the JVM/Android source set to match actual usage and support the new JVM materialization flow. |
| simdjson-kotlin/src/commonTest/kotlin/io/github/devcrocod/simdjson/DomParsingTest.kt | Adds/expands regression tests to ensure DOM results remain valid across reuse, iterate, and close. |
| simdjson-kotlin/src/commonMain/kotlin/io/github/devcrocod/simdjson/TapeValueMaterializer.kt | Removes common materializer that previously produced tape-backed JsonObject/JsonArray views. |
| simdjson-kotlin/src/commonMain/kotlin/io/github/devcrocod/simdjson/SimdJsonParser.kt | Updates parse() KDoc to explicitly guarantee returned DOM independence from the parser lifecycle. |
| simdjson-kotlin/src/commonMain/kotlin/io/github/devcrocod/simdjson/JsonValue.kt | Simplifies JsonObject/JsonArray to plain list wrappers (no tape-view mode). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (_entries != null) return _entries.iterator() | ||
| return TapeObjectIterator() | ||
| } | ||
| override fun iterator(): Iterator<Pair<String, JsonValue>> = entries.iterator() |
Comment on lines
+48
to
49
| override fun iterator(): Iterator<JsonValue> = elements.iterator() | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Audit finding B17: on the Vector backend,
parse()returned lazyJsonObject/JsonArrayviews over the parser's single reusedTape+stringBuffer. A subsequentparse()/iterate()on the same parser overwrote those buffers in place, silently corrupting previously returned DOM values (reproduced:ClassCastException, stalesize) — while theJsonValueKDoc promises "Immutable and safe to share across threads" and the parser KDoc recommends reuse. JNI/Native already materialized eagerly, so the contract also diverged across backends.Changes
TapeValueMaterializermoves commonMain → jvmMain and recursively builds list-backedJsonObject(entries)/JsonArray(elements)— the same shapes JNI/Native produce.TapeBuilder.createJsonValue()unchanged.JsonObject/JsonArraylose the tape-view representation and become plain list wrappers; public surface (size,get,keys,contains,iterator) unchanged. Net −38 lines.Tape.ktmoves commonMain → jvmAndAndroidMain (its only users areTapeBuilder/TapeValueMaterializerin jvmMain andNumberParserin jvmAndAndroidMain).parse()overloads now state the guarantee — the returned tree is fully materialized and independent of the parser, valid after subsequentparse/iterate/close. On-Demand (iterate()) keeps its documented borrow semantics.iterate()(string root deliberately clobbers the sharedstringBuffer), DOM survival afterclose().Testing
jvmTest536/536,jvmTest128536/536,jvmTestJni,macosArm64Test,testAndroidHostTest,:simdjson-kotlin-serialization:allTests— all passing locally; Linux/iOS native covered by CI.Trade-off: Vector DOM parse now allocates the object graph (the cost JNI/Native already pay; Jackson-tree equivalent). The zero-alloc "read a few fields" use case remains served by
iterate().