Sanitize ranges in code labels coming from extensions (#10307)

Without any sanitization, extensions would be able to crash zed, because
the editor code assumes these ranges are valid.

Release Notes:

- N/A
This commit is contained in:
Max Brunsfeld 2024-04-08 19:53:25 -07:00 committed by GitHub
parent a4566c36a3
commit cc367d43d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 91 additions and 39 deletions

View file

@ -292,13 +292,13 @@ fn labels_from_wit(
labels labels
.into_iter() .into_iter()
.map(|label| { .map(|label| {
label.map(|label| { let label = label?;
build_code_label( let runs = if !label.code.is_empty() {
&label, language.highlight_text(&label.code.as_str().into(), 0..label.code.len())
&language.highlight_text(&label.code.as_str().into(), 0..label.code.len()), } else {
&language, Vec::new()
) };
}) build_code_label(&label, &runs, &language)
}) })
.collect() .collect()
} }
@ -307,7 +307,7 @@ fn build_code_label(
label: &wit::CodeLabel, label: &wit::CodeLabel,
parsed_runs: &[(Range<usize>, HighlightId)], parsed_runs: &[(Range<usize>, HighlightId)],
language: &Arc<Language>, language: &Arc<Language>,
) -> CodeLabel { ) -> Option<CodeLabel> {
let mut text = String::new(); let mut text = String::new();
let mut runs = vec![]; let mut runs = vec![];
@ -315,7 +315,7 @@ fn build_code_label(
match span { match span {
wit::CodeLabelSpan::CodeRange(range) => { wit::CodeLabelSpan::CodeRange(range) => {
let range = Range::from(*range); let range = Range::from(*range);
let code_span = &label.code.get(range.clone())?;
let mut input_ix = range.start; let mut input_ix = range.start;
let mut output_ix = text.len(); let mut output_ix = text.len();
for (run_range, id) in parsed_runs { for (run_range, id) in parsed_runs {
@ -327,19 +327,18 @@ fn build_code_label(
} }
if run_range.start > input_ix { if run_range.start > input_ix {
output_ix += run_range.start - input_ix; let len = run_range.start - input_ix;
input_ix = run_range.start; output_ix += len;
input_ix += len;
} }
{
let len = range.end.min(run_range.end) - input_ix; let len = range.end.min(run_range.end) - input_ix;
runs.push((output_ix..output_ix + len, *id)); runs.push((output_ix..output_ix + len, *id));
output_ix += len; output_ix += len;
input_ix += len; input_ix += len;
} }
}
text.push_str(&label.code[range]); text.push_str(code_span);
} }
wit::CodeLabelSpan::Literal(span) => { wit::CodeLabelSpan::Literal(span) => {
let highlight_id = language let highlight_id = language
@ -356,11 +355,13 @@ fn build_code_label(
} }
} }
CodeLabel { let filter_range = Range::from(label.filter_range);
text.get(filter_range.clone())?;
Some(CodeLabel {
text, text,
runs, runs,
filter_range: label.filter_range.into(), filter_range,
} })
} }
impl From<wit::Range> for Range<usize> { impl From<wit::Range> for Range<usize> {
@ -472,13 +473,13 @@ fn extract_int<T: Serialize>(value: T) -> i32 {
fn test_build_code_label() { fn test_build_code_label() {
use util::test::marked_text_ranges; use util::test::marked_text_ranges;
let (code, ranges) = marked_text_ranges( let (code, code_ranges) = marked_text_ranges(
"«const» «a»: «fn»(«Bcd»(«Efgh»)) -> «Ijklm» = pqrs.tuv", "«const» «a»: «fn»(«Bcd»(«Efgh»)) -> «Ijklm» = pqrs.tuv",
false, false,
); );
let runs = ranges let code_runs = code_ranges
.iter() .into_iter()
.map(|range| (range.clone(), HighlightId(0))) .map(|range| (range, HighlightId(0)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let label = build_code_label( let label = build_code_label(
@ -499,22 +500,75 @@ fn test_build_code_label() {
}, },
code, code,
}, },
&runs, &code_runs,
&language::PLAIN_TEXT, &language::PLAIN_TEXT,
); )
.unwrap();
let (text, ranges) = marked_text_ranges("pqrs.tuv: «fn»(«Bcd»(«Efgh»)) -> «Ijklm»", false); let (label_text, label_ranges) =
let runs = ranges marked_text_ranges("pqrs.tuv: «fn»(«Bcd»(«Efgh»)) -> «Ijklm»", false);
.iter() let label_runs = label_ranges
.map(|range| (range.clone(), HighlightId(0))) .into_iter()
.map(|range| (range, HighlightId(0)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!( assert_eq!(
label, label,
CodeLabel { CodeLabel {
text, text: label_text,
runs, runs: label_runs,
filter_range: label.filter_range.clone() filter_range: label.filter_range.clone()
} }
) )
} }
#[test]
fn test_build_code_label_with_invalid_ranges() {
use util::test::marked_text_ranges;
let (code, code_ranges) = marked_text_ranges("const «a»: «B» = '🏀'", false);
let code_runs = code_ranges
.into_iter()
.map(|range| (range, HighlightId(0)))
.collect::<Vec<_>>();
// A span uses a code range that is invalid because it starts inside of
// a multi-byte character.
let label = build_code_label(
&wit::CodeLabel {
spans: vec![
wit::CodeLabelSpan::CodeRange(wit::Range {
start: code.find('B').unwrap() as u32,
end: code.find(" = ").unwrap() as u32,
}),
wit::CodeLabelSpan::CodeRange(wit::Range {
start: code.find('🏀').unwrap() as u32 + 1,
end: code.len() as u32,
}),
],
filter_range: wit::Range {
start: 0,
end: "B".len() as u32,
},
code,
},
&code_runs,
&language::PLAIN_TEXT,
);
assert!(label.is_none());
// Filter range extends beyond actual text
let label = build_code_label(
&wit::CodeLabel {
spans: vec![wit::CodeLabelSpan::Literal(wit::CodeLabelSpanLiteral {
text: "abc".into(),
highlight_name: Some("type".into()),
})],
filter_range: wit::Range { start: 0, end: 5 },
code: String::new(),
},
&code_runs,
&language::PLAIN_TEXT,
);
assert!(label.is_none());
}

View file

@ -1,22 +1,20 @@
mod since_v0_0_1; mod since_v0_0_1;
mod since_v0_0_4; mod since_v0_0_4;
mod since_v0_0_6; mod since_v0_0_6;
use since_v0_0_6 as latest;
use std::ops::RangeInclusive; use super::{wasm_engine, WasmState};
use std::sync::Arc;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use language::{LanguageServerName, LspAdapterDelegate}; use language::{LanguageServerName, LspAdapterDelegate};
use semantic_version::SemanticVersion; use semantic_version::SemanticVersion;
use std::{ops::RangeInclusive, sync::Arc};
use wasmtime::{ use wasmtime::{
component::{Component, Instance, Linker, Resource}, component::{Component, Instance, Linker, Resource},
Store, Store,
}; };
use super::{wasm_engine, WasmState}; #[cfg(test)]
pub use latest::CodeLabelSpanLiteral;
use since_v0_0_6 as latest;
pub use latest::{ pub use latest::{
zed::extension::lsp::{Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind}, zed::extension::lsp::{Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind},
CodeLabel, CodeLabelSpan, Command, Range, CodeLabel, CodeLabelSpan, Command, Range,