fix(editor): resolve stale text ranges before layout, highlighting and accessibility read them - #2353
Merged
Merged
Conversation
…d accessibility read them
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
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 #2340. Uses the
NSRange.resolved(inDocumentOfLength:)added in #2351, which is already merged.The problem
Four places read the text storage with a range or index that was computed against an older version of the document.
NSTextStorageandNSStringraise for those rather than clamping, and on macOS 26 and later an exception raised inside a draw or layout pass terminates the process.TextLine.prepareForDisplay(TextLine.swift:57) slices the storage withposition.rangefrom the line storage. That storage is updated from the edited range, so it can be longer than the string it indexes while an edit is still in flight, which is the same invariant that produced therectForOffsetcrash fixed in fix(editor): stop the rectForOffset crash and keep the autocomplete popup below the caret #1835. Every other consumer of it got a guard in that commit; this one, which runs from the layout pass atTextLayoutManager+Layout.swift:241, did not. Measured:NSRangeException, "NSConcreteTextStorage attributedSubstringFromRange:: Out of bounds".TextView+Accessibility.swift:141guardedindex < documentRange.lengthwith no lower bound, so a negative index from an accessibility client reachedrangeOfComposedCharacterSequence(at:), which raises.accessibilityLine(for:)next to it had the same gap.Highlighter.styleContainerDidUpdate(Highlighter.swift:279) applies style runs tracked against the storage's length as of the last edit, so a run can name text a newer edit removed. It runs inside an editing transaction on the storage.MinimapLineFragmentView.addDrawingRunsUntil(MinimapLineFragmentView.swift:82-95) readsattribute(_:at:longestEffectiveRange:in:)andcharacter(at:)at positions from its own fragment, with no bounds check. The minimap runs a second layout manager, which is exactly the one that goes stale. This one is currently unreachable in TablePro, becauseEditorPeripherals.swift:62hardcodesshowMinimap: false; it is fixed so that turning the minimap on does not turn it back into a crash.The fix
Each site resolves its range against the storage it is about to read, using the resolver added in #2351. Nothing clamps silently into a different range: where a partial answer would be wrong, the site skips the work and leaves it for the next pass.
prepareForDisplayreturns without typesetting when its range does not fit, leavingneedsLayoutset so the line is typeset again once the line storage and the string agree..notFoundand-1, which is what they already answer for an index past the end.Verification
swift test --package-path LocalPackages/CodeEditTextView: 169 tests in 17 suites pass, including 4 new ones. Against the unfixed code the newTextLineDisplayRangeTestskills the test host withNSRangeException: NSConcreteTextStorage attributedSubstringFromRange:: Out of bounds.xcodebuild -scheme TablePro build: passes, which is what compiles the CodeEditSourceEditor half of this change.The two
CodeEditSourceEditorfixes carry no tests: that package's test target does not run in CI (macos-tests.ymlrunsCodeEditTextViewonly, and the comment there records thatswift testcannot buildCodeEditSourceEditoron the runner), so a test placed there would be dead weight. Both are covered by the build and by the resolver's own unit tests in the package that does run.