mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
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:
+9
@@ -0,0 +1,9 @@
|
||||
---
|
||||
source: tui/src/history_cell/tests.rs
|
||||
expression: rendered
|
||||
---
|
||||
• Ran AAAAAAAAAAAAAAAAAA
|
||||
│ AAAAAAAAAAAAAAAAAA
|
||||
│ AAAAAAAAAAAAAAAAAA
|
||||
│ … +225 lines
|
||||
└ (no output)
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user