Repository navigation
fix: reject imported style end anchors without their style start - #1261
Draft
1AbdullahButt wants to merge 1 commit into
Draft
1AbdullahButt wants to merge 1 commit into
1AbdullahButt wants to merge 1 commit into
Conversation
A malformed update could carry a text StyleEnd whose previous op is not its StyleStart. The richtext diff calculator (`style_for_end_anchor`) then hit `unreachable!()` while the doc held its locks, leaving the doc unusable. Imports now check these anchors with the other op references of the import rollback scope and return a DecodeError, rolling back. Fixes loro-dev#1254 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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.
Fixes #1254
Summary
A malformed update can contain a text
StyleEndop whose previous op (id - 1) is not itsStyleStart. The update from the issue decodes, in a detached doc, to amark_endat counter 5 right after thehelloinsert (counter 4). The richtext diff calculator'sstyle_for_end_anchorassumesid - 1is aStyleStart. On this input it hitsunreachable!()(oras_list().unwrap()) while the doc's locks are held, and the doc is unusable afterwards.Fix
This is checked at import time, alongside the movable-list
Move/Setreference validation. The import is rejected with aDecodeErrorand rolled back, so the malformed op is not hidden.OpLog::insert_new_changerecords every TextStyleEndthat is not paired with aStyleStartimmediately before it in the same change and container. Honest marks are always paired that way, so the usual case needs no history lookup.OpLog::validate_style_end_anchors_in_import_scopechecks the recorded ends.id - 1must be aStyleStartin the same text. If it is not in the stored history, the end is accepted only on a shallow doc whose shallow-root state has the style, the same fallbackstyle_for_end_anchoruses.validate_op_refs_in_import_scoperuns both checks. It replaces the threevalidate_movable_list_elem_refs_in_import_scopecall sites: attached import, detached import, and theimport_batchreattach.import_op_can_reject(op, detached = true)now also counts TextStyleEndops, so detached imports open a rollback scope for them. Attached imports already open one for any Text op.context/import-batch-atomicity.mdand added a changeset.The outdated (RLE) import path,
update_oplog_and_apply_delta_to_state_if_needed, doesn't run the movable-list validation either. I left it alone.Tests
New
crates/loro/tests/text_style_end_invalid_ops.rs:mark_endafter an insert (attached and detached), after amarkon another text, after a map op, and as a peer's first op: allDecodeError, doc unchanged and usableStyleStartandStyleEndarrive in separateupdates_in_rangeexports: still accepted, attached and detachedWhat I could and could not run: this machine has no Rust toolchain, so I have not compiled this or run
cargo test. It is a draft until CI runs. What I did check: withloro-crdt@1.17.1from npm, I imported the issue's update into a detached doc and exported JSON. That confirmed the malformed op is amark_endat counter 5 after the counter-4 insert, which is the case this check rejects.Claude Code (AI) wrote this change; I reviewed it before opening.
🤖 Generated with Claude Code