From b5314d4026fcdedca2176b0a838c2c704542302f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 08:41:46 +0000 Subject: [PATCH] Fix Cow::Owned panic and transmute safety in SpanIter/parse_ansi_text - span.rs: Replace unreachable!() for Cow::Owned with continue (skip the span). Owned content's String lives only for the local span variable's scope, so producing Graphemes<'text> from it would create dangling references. Skipping is safe and this branch is never reached in practice. - builder.rs: Strengthen the unsafe transmute with a debug_assert that verifies all Span contents are Cow::Borrowed at runtime in debug builds, validating the safety invariant that ansi_to_tui's zero-copy parser always borrows from the input bytes. Expanded SAFETY comment explains the lifetime mismatch that makes the transmute necessary. Co-authored-by: sxyazi <17523360+sxyazi@users.noreply.github.com> Agent-Logs-Url: https://github.com/sxyazi/yazi/sessions/9bca4d59-d1bb-47dc-9799-13960d106841 --- yazi-shim/src/ratatui/builder.rs | 18 +++++++++++++----- yazi-shim/src/ratatui/span.rs | 10 ++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/yazi-shim/src/ratatui/builder.rs b/yazi-shim/src/ratatui/builder.rs index 413217df..93681b81 100644 --- a/yazi-shim/src/ratatui/builder.rs +++ b/yazi-shim/src/ratatui/builder.rs @@ -50,9 +50,17 @@ impl LineIterBuilder { } fn parse_ansi_text<'text>(s: &'text str) -> Result, ansi_to_tui::Error> { - // SAFETY: ansi_to_tui::to_text() returns slices into the input text data. - // The public API ties that borrow to the temporary method receiver instead of - // the original `&str`, so we widen it back to `'text`, which is the lifetime of - // the source string stored by `LineIter`. - unsafe { Ok(std::mem::transmute::, Text<'text>>(s.to_text()?)) } + let text = s.to_text()?; + debug_assert!( + text.lines.iter().flat_map(|l| l.spans.iter()).all(|span| { + matches!(span.content, std::borrow::Cow::Borrowed(_)) + }), + "ansi_to_tui produced Cow::Owned content; the transmute below is unsound" + ); + // SAFETY: The zero-copy parser creates Spans whose content borrows from the + // input bytes. The trait method's `'_` lifetime is tied to the method receiver + // (`&&'text str`) rather than to the underlying string data (`&'text str`), so + // we widen it back to `'text` here. The debug_assert above verifies in debug + // builds that all Spans indeed contain Cow::Borrowed content. + unsafe { Ok(std::mem::transmute::, Text<'text>>(text)) } } diff --git a/yazi-shim/src/ratatui/span.rs b/yazi-shim/src/ratatui/span.rs index 7465b388..451c231d 100644 --- a/yazi-shim/src/ratatui/span.rs +++ b/yazi-shim/src/ratatui/span.rs @@ -67,8 +67,14 @@ impl<'lend, 'text> Iterator for SpanIter<'lend, 'text> { } let span = spans.next()?; - let Cow::Borrowed(content) = span.content else { - unreachable!("SpanIter only stores borrowed text") + let content = match &span.content { + Cow::Borrowed(s) => *s, + // Owned content cannot be safely projected to 'text: the String lives + // only as long as `span` (a local variable), so grapheme references + // would dangle after this loop iteration. Skip the span rather than + // panic. In normal usage every Span contains Cow::Borrowed text, so + // this branch is unreachable when the code is used correctly. + Cow::Owned(_) => continue, }; *current = Some(CurrentSpan { style: line_style.patch(span.style),