Skip to content

Commit 5ee0c69

Browse files
authored
Use safer harp::parse_expr() to decode file path strings (#843)
1 parent d838eef commit 5ee0c69

File tree

4 files changed

+45
-57
lines changed

4 files changed

+45
-57
lines changed

crates/ark/src/lsp/completions/sources/unique/file_path.rs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@
88
use std::env::current_dir;
99
use std::path::PathBuf;
1010

11-
use harp::object::RObject;
12-
use harp::string::r_string_decode;
11+
use harp::utils::r_is_string;
1312
use harp::utils::r_normalize_path;
1413
use stdext::unwrap;
15-
use stdext::IntoResult;
1614
use tower_lsp::lsp_types::CompletionItem;
1715
use tree_sitter::Node;
1816

@@ -33,13 +31,24 @@ pub(super) fn completions_from_string_file_path(
3331
//
3432
// NOTE: This includes the quotation characters on the string, and so
3533
// also includes any internal escapes! We need to decode the R string
36-
// before searching the path entries.
34+
// by parsing it before searching the path entries.
3735
let token = context.document.contents.node_slice(&node)?.to_string();
38-
let contents = unsafe { r_string_decode(token.as_str()).into_result()? };
39-
log::trace!("String value (decoded): {}", contents);
36+
37+
// It's entirely possible that we can fail to parse the string, `R_ParseVector()`
38+
// can fail in various ways. We silently swallow these because they are unlikely
39+
// to report to real file paths and just bail (posit-dev/positron#6584).
40+
let Ok(contents) = harp::parse_expr(&token) else {
41+
return Ok(completions);
42+
};
43+
44+
// Double check that parsing gave a string. It should, because `node` points to
45+
// a tree-sitter string node.
46+
if !r_is_string(contents.sexp) {
47+
return Ok(completions);
48+
}
4049

4150
// Use R to normalize the path.
42-
let path = r_normalize_path(RObject::from(contents))?;
51+
let path = r_normalize_path(contents)?;
4352

4453
// parse the file path and get the directory component
4554
let mut path = PathBuf::from(path.as_str());
@@ -82,3 +91,28 @@ pub(super) fn completions_from_string_file_path(
8291

8392
Ok(completions)
8493
}
94+
95+
#[cfg(test)]
96+
mod tests {
97+
use crate::fixtures::point_from_cursor;
98+
use crate::lsp::completions::sources::unique::file_path::completions_from_string_file_path;
99+
use crate::lsp::document_context::DocumentContext;
100+
use crate::lsp::documents::Document;
101+
use crate::r_task;
102+
use crate::treesitter::node_find_string;
103+
104+
#[test]
105+
fn test_unparseable_string() {
106+
// https://github.com/posit-dev/positron/issues/6584
107+
r_task(|| {
108+
// "\R" is an unrecognized escape character and `R_ParseVector()` errors on it
109+
let (text, point) = point_from_cursor(r#" ".\R\utils.R@" "#);
110+
let document = Document::new(text.as_str(), None);
111+
let context = DocumentContext::new(&document, point, None);
112+
let node = node_find_string(&context.node).unwrap();
113+
114+
let completions = completions_from_string_file_path(&node, &context).unwrap();
115+
assert_eq!(completions.len(), 0);
116+
})
117+
}
118+
}

crates/harp/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ pub mod routines;
3333
pub mod session;
3434
pub mod size;
3535
pub mod source;
36-
pub mod string;
3736
pub mod symbol;
3837
pub mod sys;
3938
pub mod table;

crates/harp/src/string.rs

Lines changed: 0 additions & 48 deletions
This file was deleted.

crates/harp/src/utils.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ use crate::r_char;
3636
use crate::r_lang;
3737
use crate::r_null;
3838
use crate::r_symbol;
39-
use crate::string::r_is_string;
4039
use crate::symbol::RSymbol;
4140
use crate::vector::CharacterVector;
4241
use crate::vector::IntegerVector;
@@ -154,6 +153,10 @@ pub fn r_is_simple_vector(value: SEXP) -> bool {
154153
}
155154
}
156155

156+
pub fn r_is_string(x: SEXP) -> bool {
157+
r_typeof(x) == STRSXP && r_length(x) == 1 && x != r_str_na()
158+
}
159+
157160
/// Is `object` a matrix?
158161
///
159162
/// Notably returns `false` for 1D arrays and >=3D arrays.

0 commit comments

Comments
 (0)