Skip syntax highlighting for lines over 4 KiB (#34796)

## Why

Very long individual lines can make syntax highlighting consume excessive CPU or memory even when the overall input remains below the existing size and line-count limits.

## What changed

- Fall back to plain, unstyled text when any input line exceeds 4 KiB.
- Keep the existing 512 KiB total-size and 10,000-line guardrails.

## Testing

- Cover highlighting fallback and text preservation for a long single-line Bash command.
- Snapshot rendering of a command that exceeds the per-line limit.

GitOrigin-RevId: ba02997331a294d8a32ca89ee06acb12dd3bbd83
This commit is contained in:
Eric Traut
2026-07-22 17:38:11 +00:00
committed by copyberry
parent 2c49493b5b
commit 5381edb133
3 changed files with 59 additions and 4 deletions
@@ -0,0 +1,9 @@
---
source: tui/src/history_cell/tests.rs
expression: rendered
---
• Ran AAAAAAAAAAAAAAAAAA
│ AAAAAAAAAAAAAAAAAA
│ AAAAAAAAAAAAAAAAAA
│ … +225 lines
└ (no output)
+25
View File
@@ -7,6 +7,7 @@ use crate::exec_cell::ExecCell;
use crate::legacy_core::config::Config;
use crate::legacy_core::config::ConfigBuilder;
use crate::line_truncation::line_width;
use crate::render::highlight::MAX_HIGHLIGHT_LINE_BYTES;
use crate::session_state::ThreadSessionState;
use crate::wrapping::word_wrap_lines;
use codex_app_server_protocol::AskForApproval;
@@ -1861,6 +1862,30 @@ fn single_line_command_wraps_with_four_space_continuation() {
insta::assert_snapshot!(rendered);
}
#[test]
fn single_line_command_over_highlight_limit_uses_plain_text_fallback() {
let call_id = "c1".to_string();
let base64_like = "A".repeat(MAX_HIGHLIGHT_LINE_BYTES + 1);
let mut cell = ExecCell::new(
ExecCall {
call_id: call_id.clone(),
command: vec!["bash".into(), "-lc".into(), base64_like],
parsed: Vec::new(),
output: None,
source: ExecCommandSource::Agent,
start_time: Some(Instant::now()),
duration: None,
interaction_input: None,
},
/*animations_enabled*/ true,
);
cell.complete_call(&call_id, CommandOutput::default(), Duration::from_millis(1));
let rendered = render_lines(&cell.display_lines(/*width*/ 24)).join("\n");
insta::assert_snapshot!(rendered);
}
#[test]
fn multiline_command_without_wrap_uses_branch_then_eight_spaces() {
let call_id = "c1".to_string();
+25 -4
View File
@@ -18,9 +18,10 @@
//! swap/snapshot the theme for live preview. All highlighting functions read
//! the theme via `theme_lock()`.
//!
//! **Guardrails:** inputs exceeding 512 KB or 10 000 lines are rejected early
//! (returns `None`) to prevent pathological CPU/memory usage. Callers must
//! fall back to plain unstyled text.
//! **Guardrails:** inputs exceeding 512 KB or 10 000 lines, or containing an
//! individual line longer than 4 KiB, are rejected early (returns `None`) to
//! prevent pathological CPU/memory usage. Callers must fall back to plain
//! unstyled text.
use ratatui::style::Color as RtColor;
use ratatui::style::Modifier;
@@ -581,6 +582,9 @@ const MAX_HIGHLIGHT_BYTES: usize = 512 * 1024;
/// Skip highlighting for inputs with more than 10,000 lines.
const MAX_HIGHLIGHT_LINES: usize = 10_000;
/// Skip highlighting when an individual line is longer than 4 KiB.
pub(crate) const MAX_HIGHLIGHT_LINE_BYTES: usize = 4 * 1024;
/// Check whether an input exceeds the safe highlighting limits.
///
/// Callers that highlight content in a loop (e.g. per diff-line) should
@@ -611,7 +615,12 @@ fn highlight_to_line_spans_with_theme(
// Bail out early for oversized inputs to avoid excessive resource usage.
// Count actual lines (not newline bytes) to avoid an off-by-one when
// the input does not end with a newline.
if code.len() > MAX_HIGHLIGHT_BYTES || code.lines().count() > MAX_HIGHLIGHT_LINES {
if code.len() > MAX_HIGHLIGHT_BYTES
|| code.lines().count() > MAX_HIGHLIGHT_LINES
|| code
.lines()
.any(|line| line.len() > MAX_HIGHLIGHT_LINE_BYTES)
{
return None;
}
@@ -1139,6 +1148,18 @@ mod tests {
assert!(result.is_none(), "oversized input should fall back to None");
}
#[test]
fn long_single_line_bash_skips_highlighting_and_preserves_text() {
let token = "eHh4".repeat(MAX_HIGHLIGHT_LINE_BYTES / 4 + 1);
let code = format!("printf %s {token} | base64 -d >/dev/null");
assert!(highlight_code_to_styled_spans(&code, "bash").is_none());
assert_eq!(
highlight_code_to_lines(&code, "bash"),
vec![Line::from(code)]
);
}
#[test]
fn highlight_many_lines_falls_back() {
// Input exceeding MAX_HIGHLIGHT_LINES should return None.