Repository navigation
macOS crash: text layout receives a font run ending inside a UTF-8 character #280
Description
Problem
Zeron 0.2.48 aborted on the main UI thread while laying out text on macOS. The application log confirms an invalid UTF-8 boundary in a font run. The immediate failure is understood, but we do not yet have a reproducible user interaction or a confirmed originating UI component.
Environment and evidence
- Zeron 0.2.48, native arm64, launched from the Desktop app bundle.
- macOS 27.0 (26A5425a), Apple M4 Pro.
- Crash: September 8, 2026, 17:16:47 -0300, after approximately 3.5 hours of runtime.
- Main thread:
EXC_CRASH (SIGABRT),abort() called. - The inspected executable UUID matches the crash report:
C3ADD573-6300-301F-95F3-6260A1498D8C.
The application log at 2026-09-08T20:16:47.046826Z records:
application panic
panicked at core/src/str/mod.rs:846:21:
end byte index 2 is not a char boundary; it is inside '→' (bytes 1..4 of string)
Inspection of the matching executable's disassembly and embedded source locations maps the failing caller to layout_line in gpui_macos/src/text_system.rs:
for run in font_runs {
let text_run;
(text_run, text) = text.split_at(run.len);
// Construct the attributed string for Core Text.
}The run length ended inside the three-byte UTF-8 encoding of →. This triggered a Rust panic and the process subsequently aborted. The log's byte positions refer to the string slice being split, not necessarily the complete original document.
What remains unknown
- Which UI component supplied the invalid run: the file editor, transcript, changes panel, or another text-rendering path.
- Whether the invalid range came from stale syntax highlighting, an edit-offset calculation, or a subsequent text transformation.
- The exact user action and original text at the time of failure.
The file editor is a hypothesis, not a confirmed cause. Its adapter already contains stale_highlight_ranges_are_rejected_before_updated_unicode_is_shaped, which demonstrates that ranges from an old source can be invalid for new Unicode text, plus a source-equality check before installing highlights. That test does not reproduce this incident or prove the complete asynchronous/rendering flow is protected.
History inspection does not establish an introducing commit. The editor adapter was incorporated through #130 (03250d03). The recent sidebar animation changes in #275 and #276 do not change this adapter or the failing split. Blame on the GPUI line points to its extraction/import commit (0b18592), which is not evidence that the import author introduced the defect. No individual author or PR is being identified as responsible.
Reproduction strategy
Separate a synthetic reproduction of the failure mechanism from a reproduction of the actual application bug:
- Synthetic mechanism: use
"a→b"and a font/style run ending at byte 2."a→b".split_at(2)has the same invalid-boundary condition. Exercise the real layout entry point with incompatible runs so the test covers the rendering boundary, not only Rust's string API. This is a proposed fixture, not a reproduction we have executed in the app. - Application flow: open content mixing ASCII, arrows, accented characters, emoji, and multiple syntax styles. Exercise typing, paste, undo/redo, external reload, file switching, and wrapping/truncation while highlight work is pending. Capture the first invalid run before layout to identify the producer.
- Targeted diagnostics: validate run bounds against the exact text being shaped and record component identity, document/highlight revision, text byte length, and offending ranges. Avoid logging whole documents or sensitive file contents. This should establish whether text and styles refer to the same revision.
Proposed fix direction
First identify and correct the producer. If stale highlighting is confirmed, invalidate obsolete styles when text changes and accept asynchronous results only for the matching document identity and revision. Audit offset updates and any transformation of text alongside its runs.
Add a defensive check at an appropriate shared rendering boundary: invalid style/font runs should fall back to plain styling of the unchanged text and emit a diagnostic instead of terminating the app. Do not silently round arbitrary offsets to nearby character boundaries, which can conceal the producer bug and apply styles to the wrong text. The exact placement and fallback should be chosen after tracing the call path.
Tests to implement
- Invalid-run rendering regression:
"a→b"with a run boundary at byte 2 must be detected before an unsafe split. After hardening, the rendering path must preserve the text, fall back safely, and not panic. Ensure differing styles/fonts preserve the invalid boundary rather than having adjacent runs merged away. - Valid Unicode runs: exercise arrows, accented characters, CJK, emoji, and combining sequences with valid UTF-8 boundaries. Verify expected style coverage and successful layout. Distinguish byte-boundary validity from grapheme-level cursor behavior.
- Run contract validation: cover out-of-bounds ranges, gaps/overlaps where forbidden by the API, and inconsistent total byte coverage. Assert the relevant entry point's documented contract and safe failure behavior.
- Deterministic stale-highlight integration: hold a highlight result for revision A, edit to revision B with different UTF-8 boundaries, then deliver A. Assert A is rejected and the actual rendered runs remain valid for B. Include equal-byte-length replacements so length equality cannot masquerade as source equality. Exercise the real installation/rendering path, beyond the existing helper test.
- Edit lifecycle: insert, delete, paste, undo, and redo around styled multibyte characters; verify boundaries after each operation. Include complete text replacement/external reload and highlight-factory recreation.
- Document lifecycle: switch files, close/reopen an editor, and deliver delayed highlight results; ensure results cannot attach to a different document or an obsolete editor instance.
- Text transformations: verify wrapping and truncation preserve correspondence between transformed text and style runs. Only attribute the incident to these paths if diagnostics or a failing test supports it.
- Generated Unicode edit sequences: generate valid Unicode content and edit sequences, checking that every emitted style boundary is within bounds and on a UTF-8 character boundary before shaping.
- macOS integration smoke test: render the regression fixtures through GPUI's native text backend and exercise the identified application sequence once available.
Completion criteria
A deterministic test must identify the actual producer and fail before its fix, then pass afterward. Malformed runs must no longer close the application, while valid highlighting and the original text remain intact. Until then, the immediate panic mechanism is confirmed but the originating regression remains unassigned.
No source changes or new tests have been implemented as part of this investigation.
Activity
jsgrrchg commentedon Oct 13, 2026
Follow-up: same UTF-8 layout panic in Zeron 0.2.104
Another occurrence on October 6, 2026, at 02:32:49 -0300 (05:32:49 UTC), on macOS 27.0.1 (26A434), Apple M4 Pro / arm64. The main thread aborted with
SIGABRT. The inspected executable UUID matched the report:0683137F-5276-3466-BBC6-B30CA3815DFA.The application log records:
Inspection of the matching binary places the failing split in
MacTextSystem::layout_line, attext.split_at(run.len)in gpui_macos/src/text_system.rs:543. The caller's disassembly also contains gpui-base input layout helpers, supporting the file-editor path for this occurrence.Confirmed: this repeats the immediate UTF-8/font-run failure described in this issue. Not confirmed: the precise user action or the producer of the invalid run.
A code-backed hypothesis is stale highlighting after whole-file replacement/external reload: the editor can reset the highlighter instance while retaining a factory that captured an older source/document, then recreate it before fresh highlighting arrives. An in-memory model reproduced the invalid byte boundary with the following fixture, but the full application sequence has not been verified:
This needs a deterministic test of the actual replacement/highlighter/render lifecycle before treating stale highlighting as the root cause.
Files editor: drop stale syntax highlights after an external reload